1. 基本概念
Servlet 内存马 是通过 运行时动态注册 Servlet 的方式,将恶意 Servlet 植入 Web 容器中。
特点:
-
无文件落地:不依赖磁盘上的 class 文件或 web.xml 配置
-
驻留内存:直至容器重启前一直存在
-
隐蔽性高:通过 URL 请求即可触发,常规查杀不易发现
常见利用场景:攻击者上传一个 JSP 文件,通过反射操作 Tomcat 内部对象,在运行时注入恶意 Servlet。
2. Servlet 装载流程(开发者视角)
要理解内存马,首先需要知道 正常情况下 Servlet 是如何被加载的。
2.1 编写 Servlet 类
1 | public class HelloServlet extends HttpServlet { |
2.2 在 web.xml 中配置
1 | <servlet> |
2.3 Tomcat 加载
-
启动时,Tomcat 解析
web.xml -
加载
HelloServlet类 -
建立 URL
/hello与该 Servlet 的映射
3. Tomcat Servlet 加载流程(容器内部机制)
深入 Tomcat 源码,可以看到 Servlet 装载的具体过程。
3.1 Web 应用启动
-
每个 Web 应用对应一个
StandardContext容器 -
Tomcat 调用
ContextConfig#configureStart(),加载并解析web.xml
3.2 解析 web.xml
-
ContextConfig#configureContext(WebXml webxml)方法处理配置 -
webxml 内部结构
-
webxml.servlets:保存<servlet>定义 -
webxml.servletMappings:保存<servlet-mapping>映射关系
-
3.3 注册 Servlet 定义
1 | for (WebXml.Servlet servlet : webxml.getServlets().values()) { |
-
createWrapper()→ 创建StandardWrapper(Servlet 包装类) -
addChild(wrapper)→ 将 Servlet 注册到StandardContext
👉 此时,Servlet 已被加入容器,但尚未建立 URL 映射。
3.4 注册 URL 映射
1 | for (Map.Entry<String, String> entry : webxml.getServletMappings().entrySet()) { |
-
entry.getKey()→ URL pattern,例如/hello -
entry.getValue()→ Servlet 名称,例如HelloServlet -
存入
StandardContext.servletMappings
3.5 请求处理流程
当客户端请求 /hello:
-
Mapper组件根据 URL 找到对应的Wrapper -
Wrapper.allocate()创建或复用 Servlet 实例 -
调用
service()方法 → 分发到doGet()/doPost() -
返回响应给客户端
3.6 关键类总结
-
StandardContext:Web 应用容器,管理所有 Servlet/Filter/Listener
-
StandardWrapper:Servlet 包装类,负责生命周期管理(init/service/destroy)
-
Mapper:负责 URL 匹配和请求分发
-
ContextConfig:负责解析
web.xml并注册到容器
3.7 内存马切入点
-
正常流程:由
web.xml驱动 -
内存马流程:跳过配置文件,直接在运行时操作
StandardContext -
核心调用:
-
addChild(wrapper)→ 注入恶意 Servlet -
addServletMappingDecoded(url, name)→ 建立恶意 URL 映射
-
4. 内存马实现机制
内存马的实现步骤:
-
创建恶意 Servlet 类(执行命令/回显等)
-
获取 StandardContext
-
通过
request.getServletContext()→ 反射逐层获取内部StandardContext
-
-
创建 Wrapper 并封装 Servlet
-
将 Wrapper 注册到容器(
addChild(wrapper)) -
设置 URL 映射(
addServletMappingDecoded())
5. 示例代码(JSP 内存马)
1 | <%@ page import="java.io.IOException" %> |
6. 触发步骤
-
上传 JSP 木马到目标服务器
-
访问 JSP → 执行注入逻辑
-
访问
/memshell→ 触发恶意 Servlet -
(可选)删除 JSP 文件,内存马依然存活
7. 总结
-
开发者视角:编写 Servlet → 配置 web.xml → Tomcat 自动加载
-
容器内部视角:ContextConfig 解析 web.xml → 创建 Wrapper → 注册到 StandardContext → 建立 URL 映射
-
内存马本质:在运行时动态完成这一套流程,不依赖文件,直接在内存中注册恶意 Servlet
-
关键 API:
addChild()、