一文读懂java内存马——Servlet篇
字数 3570 2025-10-14 00:33:59

Java Servlet内存马深入解析教学文档

1. Servlet 基础概念回顾

要理解内存马,必须首先掌握Servlet的核心机制。

  • 定义:Servlet是Java EE/Jakarta EE规范的一部分,是运行在Servlet容器(如Tomcat)中的Java程序。它作为Web客户端与服务器端应用程序的中间层,基于“请求-响应”模型进行交互。

  • 核心接口:狭义上指 javax.servlet.Servlet 接口,我们通常继承 HttpServlet 类并重写其方法。

  • 关键方法

    • init(): 初始化方法,在Servlet实例创建后调用。
    • service(HttpServletRequest req, HttpServletResponse resp): 核心处理方法,根据请求类型(GET, POST等)分发给 doGet(), doPost() 等方法。
    • destroy(): 销毁方法,在容器回收Servlet实例时调用。
  • 标准运行流程

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

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

这是正常的、基于文件系统的Servlet部署方式,也是理解动态注册的对比基础。

  • 步骤

    1. 创建Servlet类:编写一个继承自 HttpServlet 的类,并重写 doGet 等方法。
      package com.ex;
      import javax.servlet.http.*;
      import java.io.IOException;
      
      public class MyServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
              resp.getWriter().println("Hello from MyServlet");
          }
      }
      
    2. 配置部署描述符(web.xml):在 web.xml 中声明Servlet并将其映射到一个URL模式。
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app version="4.0" ...>
          <servlet>
              <!-- 注册到JVM中的逻辑名称 -->
              <servlet-name>MyServlet</servlet-name>
              <!-- 对应的完整类路径 -->
              <servlet-class>com.ex.MyServlet</servlet-class>
          </servlet>
          <servlet-mapping>
              <!-- 与上述逻辑名称对应 -->
              <servlet-name>MyServlet</servlet-name>
              <!-- Web访问的URL路径 -->
              <url-pattern>/myservlet</url-pattern>
          </servlet-mapping>
      </web-app>
      
    3. 部署到容器:将应用打包成WAR文件并部署到Tomcat等容器中。
  • 静态注册的特点

    • 持久化:配置信息固化在 web.xml 文件中。
    • 文件依赖:Servlet的 .class 文件必须存在于WEB-INF/classes目录或相关JAR包中。
    • 重启失效:一旦Servlet容器重启,容器会重新读取 web.xml.class 文件来初始化应用。如果此时 .class 文件已被删除,应用将因 ClassNotFoundException 而启动失败。

3. Servlet 内存马的核心原理

内存马的本质是绕过文件系统,利用Web容器的动态注册能力,在运行时将恶意Servlet注入到JVM内存中执行

  • 核心思想:利用Java的运行时能力(如反射)和Servlet容器的API,动态地创建一个Servlet类对象,并将其注册到容器的上下文(ServletContext)中,同时绑定一个攻击者可控的URL路径。

  • 与静态注册的关键区别

    1. 无文件落地:恶意Servlet的字节码直接存在于JVM内存中,不写入磁盘。这使其具备极高的隐蔽性,传统文件扫描无法检测。
    2. 动态性:注入行为发生在应用运行期间,无需重启服务。
    3. 临时性:其存活周期与Java应用相同。一旦服务重启,注入的内存马就会失效,因为容器会从 web.xml 重新初始化应用,动态注册的信息不会被保存。
  • 一个重要的观察:原文通过一个实验揭示了内存马可行性的基础——“注册到内存中”的含义

    实验:将一个静态注册的Servlet部署并成功访问后,即使删除其源代码和编译后的 .class 文件,只要不重启Tomcat,该Servlet依然可以正常访问。

    结论:Servlet一旦被类加载器加载到JVM的方法区(Method Area),其字节码就已经常驻内存。后续的实例化、方法调用都直接基于内存中的Class对象,不再依赖磁盘上的 .class 文件。内存马正是利用了这一点。

4. Servlet 内存马的关键实现技术

原文指出实现动态注册需要利用反射机制。以下是实现步骤的详细拆解:

  1. 获取当前应用的 StandardContext 对象

    • StandardContext 是Tomcat中表示一个Web应用的核心类,它包含了所有Servlet、Filter、Listener的配置和引用。
    • 获取方式通常通过反射从当前请求的 RequestServletContext 对象中逐层挖掘。例如,可以从 request.getServletContext() 开始,反射获取其内部持有的 StandardContext 实例。
  2. 动态创建恶意的Servlet类

    • 编写一个实现恶意功能的Servlet类(例如,用于命令执行)。这个类的代码可以通过漏洞(如反序列化、文件上传、模板注入等)直接注入到内存中执行。
    • 在内存中,使用自定义的类加载器或利用当前的类加载器来定义这个恶意Servlet类。核心方法是 ClassLoader.defineClass(String name, byte[] b, int off, int len),它允许将字节数组(即类的字节码)转换为一个 Class 对象。恶意类的字节码可以预先准备好。
  3. 实例化并初始化恶意Servlet

    • 通过反射调用恶意Servlet类的 newInstance() 方法创建实例。
    • 调用其 init(ServletConfig config) 方法进行初始化,使其进入就绪状态。
  4. 创建 Wrapper 并注册到 StandardContext

    • 在Tomcat中,每个Servlet都由一个 Wrapper(通常是 StandardWrapper)对象来封装和管理。
    • 使用反射创建一个新的 StandardWrapper 实例。
    • WrapperservletClass 属性设置为我们的恶意Servlet类名。
    • WrapperservletInstance 属性设置为刚刚创建的恶意Servlet实例。
    • 调用 StandardContext.addChild(Wrapper) 方法,将这个 Wrapper 添加为 StandardContext 的子容器。
  5. 添加Servlet映射

    • 调用 StandardContext.addServletMappingDecoded(String pattern, String name) 方法。
    • pattern:攻击者希望访问内存马的URL路径(如 /shell)。
    • name:必须与之前创建的 Wrapper 的名称(Wrapper.getName())保持一致,从而建立映射关系。

完成以上步骤后,当攻击者访问 http://target.com/app/shell 时,Tomcat就会根据映射关系,将请求路由到我们动态注册的恶意Servlet实例上,从而执行内存中的恶意代码。

5. 总结与关键点

特性 静态注册Servlet Servlet内存马
持久化 依赖 web.xml 文件,持久化存在 仅存在于内存,服务重启即失效
文件依赖 依赖磁盘上的 .class 文件 无文件落地,隐蔽性极高
注册时机 应用启动时 运行时动态注入
检测难度 容易(文件扫描) 困难(需要内存分析、行为监控)
核心技术 标准部署流程 Java反射、容器API调用

关键点切勿遗漏

  1. 内存驻留:理解“类加载入JVM后即不依赖文件”是理解内存马生存能力的基石。
  2. 动态注册API:内存马并非魔法,而是通过一套公开的(或通过反射可访问的)容器API(StandardContext, StandardWrapper)完成的。
  3. 反射的作用:反射是突破访问限制、在运行时动态调用这些API和构造类的关键工具。
  4. 生存周期:内存马的生命周期与Java进程绑定,重启即清除,这是其最大的弱点,也是防御和恢复的突破口。

文档说明:本教学文档完全基于您提供的链接内容进行提炼、组织和深化,确保了技术的准确性和知识的连贯性,剔除了所有无关的描述。

Java Servlet内存马深入解析教学文档 1. Servlet 基础概念回顾 要理解内存马,必须首先掌握Servlet的核心机制。 定义 :Servlet是Java EE/Jakarta EE规范的一部分,是运行在Servlet容器(如Tomcat)中的Java程序。它作为Web客户端与服务器端应用程序的中间层,基于“请求-响应”模型进行交互。 核心接口 :狭义上指 javax.servlet.Servlet 接口,我们通常继承 HttpServlet 类并重写其方法。 关键方法 : init() : 初始化方法,在Servlet实例创建后调用。 service(HttpServletRequest req, HttpServletResponse resp) : 核心处理方法,根据请求类型(GET, POST等)分发给 doGet() , doPost() 等方法。 destroy() : 销毁方法,在容器回收Servlet实例时调用。 标准运行流程 : 客户端(如浏览器)发送HTTP请求。 Servlet容器接收并解析请求,根据URL映射找到对应的Servlet。 若该Servlet实例不存在,容器会加载其类文件、创建实例并调用 init() 方法进行初始化。 容器调用 service() 方法,进而执行相应的 doGet() 或 doPost() 方法。 Servlet在处理方法中执行业务逻辑,生成响应内容。 容器将响应返回给客户端。 当容器关闭或需要回收资源时,调用 destroy() 方法销毁Servlet。 2. Servlet 的静态注册(传统方式) 这是正常的、基于文件系统的Servlet部署方式,也是理解动态注册的对比基础。 步骤 : 创建Servlet类 :编写一个继承自 HttpServlet 的类,并重写 doGet 等方法。 配置部署描述符(web.xml) :在 web.xml 中声明Servlet并将其映射到一个URL模式。 部署到容器 :将应用打包成WAR文件并部署到Tomcat等容器中。 静态注册的特点 : 持久化 :配置信息固化在 web.xml 文件中。 文件依赖 :Servlet的 .class 文件必须存在于WEB-INF/classes目录或相关JAR包中。 重启失效 :一旦Servlet容器重启,容器会重新读取 web.xml 和 .class 文件来初始化应用。如果此时 .class 文件已被删除,应用将因 ClassNotFoundException 而启动失败。 3. Servlet 内存马的核心原理 内存马的本质是 绕过文件系统,利用Web容器的动态注册能力,在运行时将恶意Servlet注入到JVM内存中执行 。 核心思想 :利用Java的运行时能力(如反射)和Servlet容器的API,动态地创建一个Servlet类对象,并将其注册到容器的上下文( ServletContext )中,同时绑定一个攻击者可控的URL路径。 与静态注册的关键区别 : 无文件落地 :恶意Servlet的字节码直接存在于JVM内存中,不写入磁盘。这使其具备极高的隐蔽性,传统文件扫描无法检测。 动态性 :注入行为发生在应用运行期间,无需重启服务。 临时性 :其存活周期与Java应用相同。一旦服务重启,注入的内存马就会失效,因为容器会从 web.xml 重新初始化应用,动态注册的信息不会被保存。 一个重要的观察 :原文通过一个实验揭示了内存马可行性的基础—— “注册到内存中”的含义 。 实验:将一个静态注册的Servlet部署并成功访问后,即使删除其源代码和编译后的 .class 文件,只要不重启Tomcat,该Servlet依然可以正常访问。 结论 :Servlet一旦被类加载器加载到JVM的方法区(Method Area),其字节码就已经常驻内存。后续的实例化、方法调用都直接基于内存中的Class对象,不再依赖磁盘上的 .class 文件。内存马正是利用了这一点。 4. Servlet 内存马的关键实现技术 原文指出实现动态注册需要利用 反射 机制。以下是实现步骤的详细拆解: 获取当前应用的 StandardContext 对象 : StandardContext 是Tomcat中表示一个Web应用的核心类,它包含了所有Servlet、Filter、Listener的配置和引用。 获取方式通常通过反射从当前请求的 Request 或 ServletContext 对象中逐层挖掘。例如,可以从 request.getServletContext() 开始,反射获取其内部持有的 StandardContext 实例。 动态创建恶意的Servlet类 : 编写一个实现恶意功能的Servlet类(例如,用于命令执行)。这个类的代码可以通过漏洞(如反序列化、文件上传、模板注入等)直接注入到内存中执行。 在内存中,使用自定义的类加载器或利用当前的类加载器来 定义这个恶意Servlet类 。核心方法是 ClassLoader.defineClass(String name, byte[] b, int off, int len) ,它允许将字节数组(即类的字节码)转换为一个 Class 对象。恶意类的字节码可以预先准备好。 实例化并初始化恶意Servlet : 通过反射调用恶意Servlet类的 newInstance() 方法创建实例。 调用其 init(ServletConfig config) 方法进行初始化,使其进入就绪状态。 创建 Wrapper 并注册到 StandardContext : 在Tomcat中,每个Servlet都由一个 Wrapper (通常是 StandardWrapper )对象来封装和管理。 使用反射创建一个新的 StandardWrapper 实例。 将 Wrapper 的 servletClass 属性设置为我们的恶意Servlet类名。 将 Wrapper 的 servletInstance 属性设置为刚刚创建的恶意Servlet实例。 调用 StandardContext.addChild(Wrapper) 方法,将这个 Wrapper 添加为 StandardContext 的子容器。 添加Servlet映射 : 调用 StandardContext.addServletMappingDecoded(String pattern, String name) 方法。 pattern :攻击者希望访问内存马的URL路径(如 /shell )。 name :必须与之前创建的 Wrapper 的名称( Wrapper.getName() )保持一致,从而建立映射关系。 完成以上步骤后,当攻击者访问 http://target.com/app/shell 时,Tomcat就会根据映射关系,将请求路由到我们动态注册的恶意Servlet实例上,从而执行内存中的恶意代码。 5. 总结与关键点 | 特性 | 静态注册Servlet | Servlet内存马 | | :--- | :--- | :--- | | 持久化 | 依赖 web.xml 文件,持久化存在 | 仅存在于内存,服务重启即失效 | | 文件依赖 | 依赖磁盘上的 .class 文件 | 无文件落地 ,隐蔽性极高 | | 注册时机 | 应用启动时 | 运行时动态注入 | | 检测难度 | 容易(文件扫描) | 困难(需要内存分析、行为监控) | | 核心技术 | 标准部署流程 | Java反射、容器API调用 | 关键点切勿遗漏 : 内存驻留 :理解“类加载入JVM后即不依赖文件”是理解内存马生存能力的基石。 动态注册API :内存马并非魔法,而是通过一套公开的(或通过反射可访问的)容器API( StandardContext , StandardWrapper )完成的。 反射的作用 :反射是突破访问限制、在运行时动态调用这些API和构造类的关键工具。 生存周期 :内存马的生命周期与Java进程绑定,重启即清除,这是其最大的弱点,也是防御和恢复的突破口。 文档说明 :本教学文档完全基于您提供的链接内容进行提炼、组织和深化,确保了技术的准确性和知识的连贯性,剔除了所有无关的描述。