Java内存马——Tomcat Valve型的三种注入

转载自:https://www.freebuf.com/articles/web/433972.html

核心原理

  • **Tomcat Pipeline & Valve:**Tomcat 使用责任链模式处理请求。Pipeline包含多个Valve,每个Valve负责特定任务(如认证、日志、访问控制)。StandardWrapperValve(通常位于链尾) 最终调用 Servlet。
  • **StandardContext:**代表一个 Web 应用,持有其对应的Pipeline对象 (StandardContext#getPipeline())。
  • **目标:**将恶意Valve注入到目标 Web 应用StandardContext的Pipeline中,通常是插入在StandardContextValve(负责应用级路由) 和StandardWrapperValve(负责调用 Servlet) 之间,或者尽可能靠前(如紧接在AccessLogValve之后)。恶意 Valve 的invoke()方法检查特定请求特征,匹配则执行命令并截断管道(不再调用getNext().invoke()),直接返回响应。

注入方式详解

方式一:纯反射注入(无依赖)

  • 场景:攻击者已通过漏洞(如反序列化、文件上传 Webshell、其他 RCE)获得代码执行能力,但当前执行环境没有 Tomcat 的catalina.jar等库依赖(例如在Bootstrap ClassLoader或Common ClassLoader加载的类中执行)。这是最通用的方式。

  • 步骤:

    1. 获取当前线程的ContextClassLoader(通常是WebappClassLoader):

    2. 反射获取ApplicationContext(关键):

      • Tomcat 将ApplicationContext存储在WebappClassLoader的resources属性 (org.apache.catalina.webresources.StandardRoot) 的context属性中。
      • 或者通过ClassLoader的resources属性获取WebResourceRoot,再反射获取其context属性。
    3. 反射获取Pipeline对象:

    4. 反射获取addValve方法:

    5. 构造恶意 Valve 实例:

      • 将恶意 Valve 的字节码(编译后的.class文件内容)转换为byte[](可通过 Class 文件硬编码、远程加载、解码等方式)。
      • 使用当前WebappClassLoader的defineClass方法(需反射调用)在内存中定义恶意 Valve 类。
      • 实例化恶意 Valve:
    6. 将恶意 Valve 注入 Pipeline:

      • 注入位置控制:Tomcat 的addValve默认加在末尾。要插入特定位置(如开头),需反射获取Pipeline的valves数组 (StandardPipeline#valves),使用反射修改数组或调用addValve(Valve, int)(如果存在)。
  • **优点:**通用性强,不依赖 Tomcat API JAR。

  • 缺点:

    • 代码冗长,大量反射操作。
    • 需要处理defineClass的调用(protected方法)。
    • 依赖对 Tomcat 内部结构(WebappClassLoader.resources.context)的准确了解,不同 Tomcat 版本可能有差异。
    • 注入的 Valve 类由WebappClassLoader加载,在堆内存中可见。

方式二:混合方式(利用 Tomcat API & 反射)

  • 场景:攻击者获得的代码执行环境可以访问到 Tomcat 的内部 API(例如,攻击代码本身是由WebappClassLoader加载的,或者通过某些方式将catalina.jar加入了类路径)。常见于从已存在的 Filter/Servlet 型内存马或 JSP Webshell 中进行“二次注入”。

  • 步骤:

    1. 获取StandardContext(更直接):

      • 通过ApplicationContext->ServletContext的属性获取:
      • 或者通过org.apache.catalina.core.ApplicationDispatcher的WRAP_SAME_OBJECT特性(如果启用)获取lastServicedRequest/lastServicedResponse中的Context(较复杂)。
    2. **获取Pipeline对象:**同方式一。

    3. 构造恶意 Valve 实例:

      • **方式 A (ClassLoader 注入):**同方式一步骤 5,使用WebappClassLoader.defineClass。
      • **方式 B (直接实例化 - 更优):**如果能直接访问到恶意 Valve 的类定义(例如,恶意 Valve 类字节码已通过其他方式加载,或者攻击代码直接包含了这个类),可以直接new:
    4. **注入 Valve:**直接调用StandardPipeline.addValve()方法:

      • 同样可以通过反射操作valves数组控制注入位置。
  • 优点:

    • 代码相对简洁清晰(减少了反射)。
    • 效率更高。
  • 缺点:

    • 依赖 Tomcat API 环境(需要org.apache.catalina.*类可见)。
    • 如果采用方式 B 构造实例,需要解决如何让EvilValve类被加载的问题(可能仍需defineClass或依赖其他已加载的恶意类)。

方式三:Java Agent + ASM/Javassist 字节码注入(终极隐蔽)

  • 场景:攻击者具备更高权限(如上传 Agent JAR 或利用 Attach API 注入 Agent),追求极致的隐蔽性。目标是不创建新的 Valve 类,而是将恶意逻辑直接编织进 Tomcat 核心类(如StandardPipeline或某个关键 Valve)的字节码中。

  • 步骤:

    1. **注入 Agent:**通过-javaagent启动参数、VirtualMachine.attach()API 或利用已知漏洞加载恶意 Agent Jar。

    2. **实现ClassFileTransformer:**在 Agent 中注册自定义的ClassFileTransformer。

    3. **定位并修改目标类:**在transform()方法中,识别目标类(例如org.apache.catalina.core.StandardPipeline):

    4. 修改addValve或invoke逻辑 (策略):

      • **策略 A (劫持invoke):**修改StandardPipeline的invoke()方法。在方法内部遍历valves数组之前或某个关键节点(如调用StandardContextValve.invoke()前),插入恶意逻辑:检查请求特征,匹配则执行命令、构造响应并返回(跳过后续 Valve)。
      • **策略 B (伪装成现有 Valve):**修改某个不常用或非关键的现有 Valve 类(如StandardContextValve或AccessLogValve)的invoke()方法。在其原有逻辑的开头或结尾插入恶意检查逻辑。
      • **策略 C (创建“幽灵”Valve):**修改StandardPipeline的addValve()方法。使其在特定条件下(例如,添加的 Valve 类名匹配某个特殊模式或 hash)不真正添加该 Valve,而是将其保存到一个隐藏的列表中。同时修改invoke()方法,使其在调用官方valves数组前后,也遍历并调用这个隐藏列表中的“幽灵” Valve。这种方式极其隐蔽,因为常规的Pipeline.valves数组中看不到恶意 Valve。
    5. **字节码操作:**使用 ASM/Javassist 库插入恶意字节码。恶意逻辑通常包含:

      • 从Request对象获取参数/头/路径。
      • 与预设密码比较。
      • 调用Runtime.exec()或ProcessBuilder执行命令。
      • 读取执行结果,写入Response输出流。
      • 根据是否匹配密码,决定是否继续调用原始管道逻辑 (getNext().invoke())。
  • 优点:

    • **终极隐蔽性:**没有新的可疑类 (EvilValve) 被定义和加载。恶意逻辑“溶解”在 Tomcat 官方核心类中。
    • 不依赖WebappClassLoader,应用重启后只要 Agent 仍在就有效(持久化能力强)。
    • 极难通过常规内存 dump 分析发现(需要逐类反编译校验)。
  • 缺点:

    • 实现难度极高,需要深入理解 JVM 字节码和 Tomcat 内部流程。
    • 需要获取 Agent 注入的权限(通常意味着已有较高权限)。
    • 不同 Tomcat 版本的核心类字节码差异较大,需要为不同版本定制或做兼容。
    • Agent 本身的存在可能被检测(JVM 参数、VirtualMachine.list())。

纯反射注入

一、核心原理与目标

  1. 目标:在不引入Tomcat API依赖(catalina.jar等)的情况下,通过纯反射操作:
    • 获取当前Web应用的StandardContext(Tomcat核心容器对象)
    • 定位其Pipeline(请求处理管道)
    • 动态注入恶意Valve实例到管道中
  2. 技术挑战:
    • 绕过类加载器隔离(从非Web类加载器访问Web层对象)
    • 通过反射链破解Tomcat内部数据结构
    • 内存中定义恶意Valve类(无磁盘文件)

二、注入流程详解

步骤1:获取WebappClassLoader

  • 原理:Tomcat为每个Web应用创建独立的WebappClassLoader,当前线程的ClassLoader通常就是它。
  • 注意:在非请求线程(如反序列化触发的线程)中需遍历线程组定位。

步骤2:反射获取StandardContext

这是最核心的步骤,需穿透两层隐藏引用:

  • 关键路径: WebappClassLoader→resources(StandardRoot) →context(StandardContext)

步骤3:获取Pipeline对象

步骤4:定义恶意Valve类(内存加载)

方案A:硬编码字节码(推荐)

方案B:动态生成字节码(ASM/Javassist)

步骤5:实例化并注入Valve

步骤6(可选):控制注入位置

  • 位置策略:插入在StandardContextValve之前(通常索引1)确保捕获所有请求。

三、恶意Valve类实现模板

四、技术难点与规避方案

  1. ClassLoader穿透问题

    • 场景:在BootstrapClassLoader中执行(如反序列化漏洞)

    • 方案:通过线程上下文类加载器传递

  2. Tomcat版本兼容性

    • StandardRoot路径变化(Tomcat 8.0+):

  3. 内存马隐身技巧

    • 类名伪装:com.sun.tools.javac.util.Context(仿JDK类)
    • 字节码加密:运行时解密后再defineClass
    • 惰性加载:首次匹配密码时才初始化命令执行逻辑

五、检测与防御手段

检测方案

  1. Heap Dump分析

    • 定位StandardPipeline.valves数组中异常Valve
  2. RASP监控点

    • 拦截ClassLoader.defineClass()调用
    • 监控StandardPipeline.addValve()反射调用栈
    • 检测非初始化阶段新增的Valve
  3. 行为特征检测

    • 请求头包含X-TOKEN等固定标记
    • 无关联页面的HTTP请求返回命令输出

防御措施

  1. 策略限制

    • 禁止反射调用defineClass()(SecurityManager)
    • 锁定StandardPipeline.valves数组写权限
  2. 运行时加固

混合方式(利用 Tomcat API & 反射)

一、混合注入的核心优势

  1. 效率与稳定性的平衡:
    • 使用Tomcat API直接调用核心方法,减少反射操作
    • 对关键路径使用反射突破访问限制
    • 比纯反射方式更稳定,减少版本兼容问题
  2. 降低检测风险:
    • 减少反射调用次数,避免触发RASP的反射监控
    • 直接API调用混入正常业务逻辑中更隐蔽
  3. 开发便利性:
    • 代码可读性更高
    • 调试和维护更简单

二、混合注入详细流程

前置条件

  • 已获得执行环境(如通过JSP WebShell或反序列化漏洞)
  • Tomcat API库(catalina.jar)在类路径中可用
  • 当前ClassLoader是WebappClassLoader

步骤1:获取StandardContext对象(混合方式)

方法A:通过ServletContext(推荐)

方法B:通过ClassLoader(无request时)

步骤2:获取Pipeline对象(直接API)

步骤3:创建恶意Valve实例

方法A:动态类定义(无文件落地)

方法B:字节码注入(ASM增强)

步骤4:注入Valve到管道(API+反射)

三、高级隐蔽技术

1. Valve伪装技术

2. 自保护机制

3. 上下文感知触发

四、检测与防御方案

检测技术

  1. 运行时管道分析

  2. 字节码校验技术

防御策略

  1. Tomcat配置加固

  2. 运行时保护机制

  3. 权限最小化

    tomcat.policy内容:

五、混合注入的演进趋势

  1. 模块化加载

  2. 云环境适配

  3. API网关集成

总结

Tomcat Valve型内存马的混合注入方式代表了当前高级威胁的典型手法:

  1. API与反射的精准结合- 在保持隐蔽性的同时提高可靠性
  2. 上下文感知的攻击逻辑- 基于环境动态调整行为
  3. 多层防御规避- 从类加载到管道操作全面伪装

Agent + ASM/Javassist 字节码注入

一、攻击流程详解

阶段1:Agent注入(JVM渗透)

阶段2:Agent核心逻辑

二、字节码修改技术(ASM核心实现)

1. StandardPipeline类修改

2. 嵌入式恶意逻辑类

三、隐蔽性增强技术

1. 幽灵Valve技术

2. 动态代码解密

3. 环境感知伪装

四、检测防御策略

1. 类完整性验证

2. 运行时行为监控

3. JVM层防护

tomcat.policy示例: