Java 反序列化是我进阶阶段啃的第一块硬骨头——概念比 PHP 那边抽象,链子全靠工具生成,一开始完全是黑盒。这篇按我自己的理解顺序整理,从机制到 CC6、Shiro 550、再到内存马。
Java 序列化机制速览
- 实现
Serializable接口的对象可以ObjectOutputStream.writeObject()序列化成字节流,readObject()还原。 - 识别特征:字节流以
AC ED 00 05开头,Base64 后是rO0AB——Cookie/参数里看到rO0AB就要警觉。 - 类可以自定义
readObject(),反序列化时会被自动调用——这就是和 PHP 魔术方法对应的”引爆点”。
CC6 链到底在干什么
CommonsCollections 的 CC 链里我最早看懂的是 CC6,因为它的路径最短:
java.util.HashMap.readObject()
→ hash(key)
→ TiedMapEntry.hashCode()
→ getValue()
→ LazyMap.get()
→ ChainedTransformer.transform()
→ InvokerTransformer 反射执行任意方法
我的理解方式:transformer 链就是一条”延迟执行的管道”,每个 Transformer 把自己能做的事转交给下一个,最后 InvokerTransformer 用反射执行 Runtime.exec()。HashMap 只是提供了一个”反序列化时必然触发”的入口。
工具直接用 ysoserial 生成,但建议至少跟一遍 CC6 的调用栈,不然用工具心里没底。
Shiro 550(rememberMe 反序列化)
- 原理:Shiro 的 rememberMe Cookie 是 AES 加密 + Base64 的序列化对象。老版本默认硬编码密钥(
kPH+bIxk5D2deZiIxcaaaA==),拿到密钥就能自己加密恶意序列化数据。 - 我的验证流程(靶场环境):
- 抓包看 Cookie 有没有
rememberMe=deleteMe回显(判断 Shiro 框架) - 用 known-key 字典跑密钥(Shiro 721/550 工具都集成这步)
- 命中后选链生成 payload 加密写入 Cookie 发送,DNSLog 收回显
- 抓包看 Cookie 有没有
- 注意点:CC 链依赖目标 classpath 里有对应版本的 CommonsCollections,打不通先换链(CC2/CC4/CC6/CommonsBeanutils),别死磕一条。
冰蝎与内存马
传统文件型 WebShell 落地就有文件特征,内存马直接把恶意代码注册进运行中的容器(Filter/Servlet/Listener 型),不落盘、重启即消失:
- Filter 型:动态注册一个 Filter 插到 FilterChain 最前面,请求先进内存马
- 冰蝎的流量特征(防守视角):固定 Accept 头、Content-Type 怪异、AES 密钥协商阶段的固定报文
- 查杀思路:比对 FilterMap 里出现陌生 Filter、dump 内存分析、用专门的内存马查杀工具(如河马 WebShell 查杀、arthas 排查)
学习建议
Java 反序列化别一开始就啃 CC1(CommonCollections 最长那条),我的顺序是:URLDNS(看懂入口)→ CC6(最短链)→ Shiro 550(实战形态)→ 再回头看 CC1-CC5。环境用 vulhub 的 Shiro 靶场,半小时能起。