Java 内存马(一):基本原理与运行时驻留

Java 内存马不是某个固定类,也不是一种特定 Payload。它是一类把处理逻辑放进正在运行的 Java Web 进程,并挂入请求链、框架路由或 JVM 执行过程的技术。

理解内存马只需要先抓住三件事:

1
2
3
请求能不能到达它
对象会不会一直存活
触发后会执行什么行为

后续所有 Servlet、Filter、Listener、Valve、Handler、Controller 和 Agent 型内存马,都建立在这三个问题上。

本系列只用于授权靶场、安全研究、代码审计和应急响应。

一、内存马与普通 WebShell 有什么区别

普通 WebShell 通常依赖磁盘文件:

1
请求 → JSP、Class 或脚本文件 → 执行

内存马的主要执行入口已经进入运行时:

1
请求 → JVM 中的处理组件 → 执行

因此,“磁盘上没有 JSP”不代表不存在后门,“重启后消失”也不代表问题已经解决。

对比项 普通 WebShell Java 内存马
主要载体 磁盘文件 JVM 中的对象或已修改类
请求入口 文件映射 请求链、框架路由或 JVM 执行点
常规文件扫描 较容易发现 可能完全没有对应文件
重启影响 文件仍在时通常继续存在 纯内存对象通常消失
排查重点 文件内容与访问日志 运行时注册、对象引用与字节码

“内存”描述的是主要驻留状态,不代表植入过程绝对无文件。攻击者仍可能使用临时 JAR、上传文件、启动参数或 Agent 完成注入,然后删除初始文件。

二、内存马为什么能驻留

2.1 请求可达

一个对象存在于堆中,不代表它能够处理请求。它必须进入真实执行路径:

1
2
3
4
请求
→ 匹配规则
→ 处理组件
→ 后续业务逻辑或提前返回响应

Filter 需要进入过滤链,Valve 或 Handler 需要进入容器请求链,Controller 需要进入框架路由。没有这条可达关系,就只是一个普通内存对象。

2.2 长期持有

局部变量会在调用结束后失去作用。内存马要持续存在,必须被生命周期更长的对象引用,例如:

  • Web 应用上下文;
  • 中间件请求链;
  • Spring 容器;
  • ClassLoader;
  • 静态字段或后台线程;
  • Instrumentation。

只要这些对象仍然持有它,垃圾回收器就不会释放它。

1
2
3
4
JVM 根对象
→ 容器、框架或线程
→ 注册表或请求链
→ 驻留对象

2.3 条件触发

内存马通常不会处理所有请求,而是判断特定条件:

  • URL Path;
  • Header;
  • Cookie;
  • 参数名或参数值;
  • 请求方法;
  • 特定事件或方法调用。

条件不满足时继续正常链路,满足时才执行异常逻辑。这也是普通业务访问往往不报错的原因。

三、常见类型与版本边界

Java Web 请求可以粗略分为四层:

层级 常见类型 特点
Servlet 规范层 Servlet、Filter、Listener 概念通用,不同容器内部保存方式不同
容器原生层 Tomcat Valve、Jetty Handler、Undertow HttpHandler 更靠近请求入口
框架层 Spring Controller、Interceptor、Handler 与应用框架绑定
JVM 层 Agent、Transformer、被修改的类 可能不增加任何 Web 组件

分析版本问题时不要把所有差异都归因于 JDK:

1
2
3
4
中间件产品和版本  → 决定对象挂在哪里
Servlet API 版本 → 决定使用 javax 还是 jakarta
JDK 版本 → 影响反射、模块、Attach 和字节码
JVM 启动参数 → 决定部分访问与诊断能力是否开放

例如,javax.servlet.* 与 jakarta.servlet.* 是 Servlet API 代际变化,不是 JDK 8 与 JDK 17 的直接区别。判断代码能否运行时,必须同时确认这四项。

四、入口、驻留和恢复必须分开

攻击者先通过漏洞或已有权限获得一次代码执行,再把处理逻辑挂入运行时:

1
2
3
4
5
6
文件上传、反序列化、JNDI、表达式注入、管理面或 Agent
→ 获得代码执行
→ 获取容器、框架或 JVM 上下文
→ 注册组件或修改已有类
→ 建立长期引用
→ 等待条件触发

这里必须区分三个概念:

概念 含义
植入入口 攻击者最初如何获得代码执行能力
驻留位置 异常对象或字节码最终挂在哪里
复活点 清除当前对象后,什么机制能再次植入

删除一个 Filter,只能说明一个驻留点被移除;如果漏洞、Agent、恶意制品或管理面权限仍然存在,内存马还会再次出现。

完整处置顺序应是:

1
2
3
4
5
6
7
隔离节点
→ 保存运行时证据
→ 定位植入入口
→ 清除驻留对象
→ 删除复活点
→ 从可信制品恢复
→ 重新验证请求链

到这里,Java 内存马的基础模型已经确定:请求可达决定能否触发,长期引用决定能否驻留,异常行为决定是否构成安全问题。后续文章只是把这套模型分别落到不同组件和中间件中。

上一篇
下一篇