『Java内存马』Java内存马攻防技术
在上一篇文章中,我们介绍了java内存马基础知识,以及如何使用RASP技术检测java内存马。本文主要介绍 Java 内存马攻防技术,包括对Java内存马实现原理与现有检测方法的研究。
Java内存马的实现原理
在现有的 Java 内存马攻击中,除了 Agent 型内存马,其他类型的 Java 内存马通常是通过利用Web容器、中间件的内部组件实现恶意对象的加载,例如 Listener、Filter、Servlet 或 Valve 等。这些组件是容器运行时的核心部分,攻击者通过反射技术操作这些组件,可以将恶意代码注册到容器的运行环境中。
例如,攻击者可以通过反射动态创建一个恶意的Servlet实例,并将其映射到特定的 URL 路径,或者注册一个恶意的 Filter,使其在请求处理过程中被触发。以下是一个具体的代码示例,展示了如何通过反射技术动态注册一个恶意的 Servlet 到 Tomcat 容器中:
1 | public class MemoryServlet extends HttpServlet { |
在上述代码中,MemoryServlet是一个简单的恶意Servlet,它在接收到HTTP请求时会触发一个命令执行操作(例如打开计算器)。通过反射技术,攻击者可以获取Tomcat的StandardContext对象,并调用addChild方法将恶意Servlet注册到容器中。同时,通过addServletMappingDecoded方法将恶意Servlet映射到一个特定的路径(如/trigger),这样当攻击者访问该路径时,就会触发恶意逻辑。
而 Java 内存马的利用点往往隐藏在Web应用的Controller层中,其触发机制通常依赖于特定的HTTP请求或参数,这是其与传统Web攻击相似的地方。攻击者会设计一个特定的触发条件,例如通过在HTTP请求中包含某个特定的参数或路径来激活恶意代码。以下是一个简单的代码示例,展示了如何通过HTTP请求触发恶意逻辑:
1 |
|
在上述代码中,TriggerServlet 是一个简单的 Servlet,它通过检查HTTP请求中的cmd参数来触发恶意逻辑。这种触发机制的设计使得内存马能够在不被轻易发现的情况下,根据外部输入动态执行恶意操作,如命令执行、数据窃取等。由于触发条件通常隐藏在HTTP请求中,且恶意代码仅存在于内存中,因此内存马的触发行为难以被传统的安全工具检测到。这种隐蔽性和动态性使得内存马成为一种极具威胁且难以防范的攻击手段。
Java内存马的分类
目前主流的Java内存根据实现原理可以分为传统Web应用型内存马、框架型内存马、中间件型内存马、Agent型内存马和新型内存马五类。其中,传统Web应用型内存马主要通过Java EE原生的Servlet-API来实现动态注册,从而实现恶意行为。框架型内存马主要利用各种如Spring的主流开发框架的特性进行恶意组件的恶意动态注册。中间件型内存马通过劫持中间件注入恶意代码,将其注册为中间件的关键组件,从而在无文件落地的情况下实现恶意操作。Agent型内存马利用Java Agent技术实现内存马逻辑的植入,具有变体众多、扩展性强的特点。新型内存马则通过各种新型技术,利用JVM底层机制、各类通信协议和各种框架机制进行内存马逻辑的深度植入,将攻击逻辑更深地嵌入JVM运行时,显著提升其隐蔽性。
传统Web应用型内存马
传统的Web应用型Java内存马通常依赖于Java EE中的组件实现恶意字节码的注入。Java EE作为Java的企业级扩展,在Java SE的基础上扩展了一套标准化的技术规范和API,从而构建企业级的应用程序,这其中也包括Web服务的支持。
Java Servlet API为Java EE中规定的组件,主要用于Web请求和响应,为构建Java Web应用的核心技术。其最常用的主要组件有Servlet、Filter、Listener等。其中,Servlet为服务端的Java应用程序,用于处理具体的HTTP请求和响应,主要处理业务逻辑;Filter是介于Web容器和Servlet之间的过滤器,主要对请求和响应进行拦截和过滤,多用于数据预处理、后处理或权限控制等。在请求到达Servlet之前,会先被一系列的Filter拦截进行处理。同样,当响应从Servlet返回时,也会通过一系列的Filter进行响应的处理再返回;Listener是用于监听某些Web应用中事件的监听器,如应用启动、关闭、会话创建、销毁等,当特定动作发生后,监听该动作的监听器就会自动调用对应的方法。Listener常常被用于管理应用的生命周期。
当我们在请求一个实现了Servlet API规范的Java Web应用时,程序会首先自动执行Listener监听器的内容,再去执行Filter过滤器。当存在多个过滤器时,则会组成过滤链,最后一个过滤器将会去执行Servlet的service方法,过程可以大致表现为Listener->Filter->Servlet。传统Web应用型内存马的技术本质是对该请求处理链的动态劫持。通过利用Java Web应用的核心处理逻辑,将恶意代码动态注入到Servlet、Filter和Listener等关键组件中,从而在内存中构建可持久化控制的恶意通道。
通常情况下,Servlet、Filter和Listener的配置在配置文件和注解中,如若需要在他处注册,可通过调用Java EE定义的Servlet API的相关接口。然而,此种方法一般只能在应用启动时阶段完成注册,运行时动态注册可能不被支持且被认为线程不安全。因此,主流的传统Web应用型内存马实现方式多使用中间件提供的相关接口,如,通过Tomcat多次反射获取StandardContext对象并利用其在Web应用运行时进行恶意类的注入。
Servlet 型内存马
Servlet型内存马原理:通过运行时动态注册恶意Servlet,并实现恶意路由的注册,从而实现恶意HTTP请求的处理。
其存在动态注册、路由劫持、内存驻留等特点。其中,动态注册指其绕过了web.xml或注解,实现运行时注入Servlet;路由劫持指其通过绑定高优先级的URL或注册新的恶意URL,实现合法路由的覆盖或恶意路由的隐藏;内存驻留指恶意Servlet类全程驻留内存,无文件落地,规避了传统的查杀方案。
一个经典的Servlet型内存马的基本流程:首先获取ServletContext;进一步地,获取Tomcat所对应的StandardContext;接着构建Servlet Wrapper;最后,将构建好的Wrapper添加到StandardContext,并加入Mappings,实现恶意路由注册。
ServletContext
: 开发者用的接口,是对容器内部实现的一层“包装”或“门面”。StandardContext
:是 Tomcat 中用于表示一个完整 Web 应用的容器组件。它是Context
接口的标准实现,负责管理这个 Web 应用中的所有 Servlet、Filter、Listener、资源路径、会话等信息,可以控制 Filter、Listener、Session 配置等
此种实现方式的内存马利用了Tomcat中间件。作为Java Web生态中广泛应用的Servlet容器,Tomcat通过分层式容器模型管理Servlet生命周期,共拥有四种类型的容器,从上到下分别为Engine、Host、Context、Wrapper。每一个Wrapper实例表示了一个具体的Servlet定义,StandardWrapper是Wrapper接口的标准实现类(StandardWrapper的主要任务就是载入Servlet类并且进行实例化),Context作为Web应用的逻辑载体,内部维护的Wrapper队列实质上构成了Servlet实例的孵化池。当攻击者通过反射机制获取StandardContext对象后,便可绕过常规部署流程,直接创建StandardWrapper实例,将Servlet类名及URL定义等植入容器,从而绕过web.xml实现运行时注入。
下面是一个典型的Servlet内存马的实现方式。首先,定义恶意Servlet类,并定义构造函数。使其在注入后可以将自己注册到StandardContext中。
1 | public class EvilServlet extends HttpServlet { |
然后,检查当前是否有存在同名的Servlet注册,如果没有的话就通过递归反射继续获取StandardContext:
1 | // 新建一个空 StandardContext, 用于存储 Tomcat 的 StandardContext 实例 |
Tomcat 中的
ServletContext
是一个接口,实际上在运行中是由多个不同类层层包装(装饰器模式)实现的,如:
1
2
3
4 ApplicationServletContext
→ ApplicationContextFacade
→ ApplicationContext
→ StandardContext这些封装层使得开发者无法直接访问 Tomcat 核心的
StandardContext
,因此需要通过反射递归访问私有字段,逐层剥离,直到找到真实的StandardContext
对象。所以这段代码中,每轮循环都尝试往内层剥离
ServletContext
,得到的对象可能是另一个ServletContext
的实现,也可能已经是StandardContext
,递归直到找到StandardContext 对象为止
获取StandardContext后,创建一个Servlet包装器,用于封装恶意Servlet:
1 | // 创建一个新的 Servlet 包装器 (Wrapper),用于封装 Servlet |
最后,定义恶意Servlet类的核心方法,doGet,用于处理HTTP请求,其具体作用为实现任意代码执行并回显在页面。为简洁起见,后文将省略类似恶意方法或恶意代码逻辑的实现。
1 | // Servlet 的核心方法,用于处理 HTTP GET 请求 |
Filter型内存马
当目标系统采用URI白名单验证机制时,通过传统Servlet型内存马注入新路径的攻击方法将彻底失效,因为所有未经验证的访问路径都会被安全网关拦截,造成内存马无法被外部访问的情况。而Filter型内存马可以绕过这种防护手段。
Filter型内存马原理:攻击者动态创建一个含有恶意代码的Filter并将其放在Filter链的头部,那么该Filter就会最先被执行,实现对所有经过容器的请求实施无差别监听
Filter容器用于对请求和响应进行过滤和处理。客户端的请求在传递到Servlet之前会先经过Fliter。那么,如果攻击者动态创建一个含有恶意代码的Filter并将其放在Filter链的头部,那么该Filter就会最先被执行,实现Filter型的内存马。即,恶意Filter的注入将使得攻击者无需依赖特定路由,只需在Filter链的头部插入自定义逻辑,即可对所有经过容器的请求实施无差别监听,从而规避目标系统的路径校验机制。
一个典型的Filter型内存马的的基本流程:首先,获取ServletContext;进一步地,获取Tomcat所对应的StandardContext;然后,定义新的恶意Filter类,内嵌恶意代码;之后,实例化新的FilterDef,并通过StandardContext.addFilterDef()注册在应用上下文中;最后,实例化新的FilterMap类,将恶意Filter和urlpattern相对应,并通过standardContext.addFilterMap()注册在应用上下文中。
下面来分析一个恶意Filter的实现方式。首先,定义一个EvilFilter类。
1 | public class EvilFilter { |
定义一个私有函数,实现获取StandardContext的作用,实现原理如上文。
1 | private StandardContext getStandardContext() throws NoSuchFieldException, IllegalAccessException { |
之后,在构造函数中定义一个新Filter,实现恶意Filter的定义,然后,将恶意Filter注册进StandardContext,并将其应用于任意URL。
1 | String filtername = "filtertrojan"; |
Listener型内存马
Listener基于特定事件触发,基于不同类型的Listener会在不同时间触发。而在一系列的Listener中,对于内存马而言最好用的是ServletRequestListener。ServletRequestListener会在每次请求传入时触发。其存在两个核心方法:requestInitialized和requestDestroyed。前者在每次请求进入时触发,适合记录请求日志或统计访问量;后者在请求处理完成、即将返回响应时触发,可以用于释放与请求相关的资源。
一个典型的Listener型内存马的基本流程:首先,继承或编写一个Listener;其次,获取 StandardContext
;最后,通过StandardContext
的添加listener接口添加恶意Listener。
下面来分析一个经典的恶意Listener实现方式。首先,定义一个EvilListener类,并定义构造函数和一个获取StandardContext的私有函数,后者实现方式如上文,此处不再赘述。
1 | public class EvilListener implements ServletRequestListener { |
之后,在构造函数中新定义一个ServletRequestListener类,并写入恶意逻辑:
1 | StandardContext standardContext = getStandardContext(); |
最后,将这个恶意Listener类注入StandardContext,从而在每次请求进入时触发恶意逻辑。
框架型内存马
Java Web框架是为了简化基于Java的Web应用程序开发而设计的工具集合。它们通过封装底层技术细节,提供更高层次的抽象和标准化开发模式,从而使开发者快速构建一个方便维护的Web系统。目前,Spring框架是Java生态中应用最广泛的企业级开发框架。Spring框架的Web模块,即Spring MVC,采用了经典的模型-视图-控制器模式,实现了典型的MVC架构模式,将应用程序的业务逻辑、视图和控制器分离,确保各个组件的职责单一,提高代码的可维护性和扩展性。
Spring MVC由三种模式组成:模型(Model)、视图(View)和控制器(Controller)。其中,控制器包含应用的核心业务逻辑和数据。模型的职责是处理数据并将其发送到视图层。Spring通常通过Service和DAO层来实现模型逻辑;View为显示数据给用户的部分。Spring MVC支持多种视图技术,例如JSP、Thymeleaf、Velocity等,可以根据不同的需求选择合适的视图渲染技术;控制器层用于负责处理用户请求,承担请求分发与响应协调的核心职能,其将模型数据传递给视图层进行显示。控制器是MVC框架的核心部分,Spring MVC使用@Controller注解标记控制器类,并通过@RequestMapping注解映射请求路径。
由于Spring的广泛应用度和其强大的灵活性和可扩展性,针对Spring机制的框架型内存马应运而生。主流的Spring框架内存马有三种,Controller型、Interceptor型和WebFlux型。
Spring Controller型内存马
Spring Controller是Spring MVC框架中处理HTTP请求的核心组件,其通过@Controller或@RestController(REST API)注解标记,负责接收客户端请求、协调业务逻辑并生成响应。Spring Controller可利用@RequestMapping及其衍生注解(如@GetMapping、@PostMapping)将特定URL路由映射到Java方法,支持从请求参数、路径变量、请求体中自动绑定数据到方法参数,并通过返回字符串(视图名称)、ModelAndView对象或@ResponseBody注解的Java对象(如JSON数据)实现页面渲染或RESTful API的数据交互,是连接前端请求与后端服务的核心枢纽。
Spring Controller型内存马原理:用户的请求是通过Controller处理的。Controller型内存马就是通过注入恶意Spring Controller来实现内存马逻辑。
经典的Controller内存马基本流程大概为:首先利用Spring的机制获取当前请求的DispatcherServlet上下文;然后劫持Spring负责处理路由的组件,RequestMappingHandlerMapping,进行恶意注入逻辑的路由绑定;之后,通过访问恶意注入的路由,实现恶意Controller类的注入。
下面来分析一个恶意Controller的实现方式(所需环境为Spring版本2.6以下):首先,定义一个TestEvilController,用于动态注入内存马。在其中定义一个触发注入逻辑的入口端点,此处为/inject;再定义一个内嵌的Controler类,用于嵌入恶意逻辑。
1 | // 声明Controller |
然后,在inject()中实现触发注入的逻辑,实现访问/inject即可触发恶意Controller的注入,进而访问恶意Controller对应的路由即能触发恶意逻辑。
1 | // 定义恶意路由的URL路径 |
根据该例的分析,会发现此种实现方式的内存马需要新注册路由,如果服务器采用了路由白名单机制,攻击者就难以访问到该路由,造成攻击失败。
Spring Interceptor型内存马
Interceptor型内存马原理:Spring Interceptor是Spring框架中的一种机制,允许在处理HTTP请求之前和之后执行特定的代码。Interceptor型内存马就是利用这个机制的内存马
Interceptor的工作原理类似于Filter,但它更适合应用于Spring MVC的请求处理流程。Spring Interceptor主要有三个核心的方法:preHandle、postHandle和afterCompletion。其中,preHandle在Controller执行之前调用;postHandle在Controller执行之后且在View渲染之前调用;afterCompletion在View渲染完成后调用。在Interceptor写完后,需要注册到Interceptor链中。
经典的Interceptor型内存马实现的具体流程大致为:首先,获取DispatchServlet上下文;之后,获取AbstractHandlerMapping的adaptedInterceptors属性,进而获取adaptedInterceptors字段的值(即当前的Interceptor列表);然后,遍历当前的Interceptor列表,检查是否已被注入本恶意代码,如若没有,就创建一个新的恶意Interceptor类加入至Interceptor列表,实现内存马的注入。
下面来分析一个恶意Interceptor的实现方式:首先,创建一个EvilInterceptor类,继承HandlerInterceptor。此处的私有构造器的作用为避免其它类使用它来创建EvilInterceptor实例;preHandle用于实现恶意逻辑。
1 | public class EvilInterceptor implements HandlerInterceptor { |
之后,编写无参构造函数,利用反射获取adaptedInterceptor:
1 | WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT",0); |
进一步地,获取adaptedInterceptors字段的值(即当前Interceptor列表):
1 | java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) field.get(abstractHandlerMapping); |
遍历这个列表,检查是否已经注入了本类(EvilInterceptor),防止递归调用。
1 | for (int i=adaptedInterceptors.size()-1; i>0; i-=1){ |
最后,创建一个EvilInterceptor类实例,加入Interceptor列表,实现内存马的注入。
1 | EvilInterceptor evil = new EvilInterceptor("Evilinterceptor"); |
中间件型内存马
中间件型内存马原理:中间件型Java内存马是一种依赖于Java中间件进行恶意字节码注入的内存马。其核心原理是通过注入恶意代码到中间件的内存中,绕过传统的文件检测机制,利用中间件自身的合法功能加载并执行恶意操作。
这类内存马通常依赖中间件的动态组件加载机制实现持久化。例如,攻击者可能通过漏洞或权限提升手段,向中间件动态注册恶意的Servlet、Filter或Listener。以Servlet内存马为例,攻击者可以创建一个继承自HttpServlet的恶意类,并将其注册到中间件的Servlet容器中,从而在特定URL路径被访问时触发恶意代码的执行。类似地,Filter内存马会通过拦截所有请求实现攻击,而Listener内存马则可能利用HTTP请求或会话事件作为触发条件。此外,某些情况下攻击者还可能通过注入JSP代码片段,利用中间件的JSP编译机制直接在内存中生成动态恶意页面。
Tomcat Valve型内存马
Tomcat Valve内存马不同于Listener、Filter和Servlet型内存马。Valve型内存马是在Pipeline之中的一个流程。其中,Tomcat中的Pipeline机制是指当Tomcat接收到客户端需求时,首先会使用 Connector 进行解析,然后发送到 Container 进行处理。而Tomcat的Pipeline机制主要是用于给在不同子容器中流通的请求添加各种不同的业务逻辑,并提前在不同子容器中完成相应的逻辑操作。
如下图所示,Valve是Pipeline中最基础的操作单元,它始终位于末端(最后执行),它在业务上面的表现是封装了具体的请求处理和输出响应。
在Tomcat中,四大组件 Engine、Host、Context 以及 Wrapper 都有其对应的 Valve 类,StandardEngineValve、StandardHostValve、StandardContextValve 以及 StandardWrapperValve,他们同时维护一个 StandardPipeline 实例。因此,Valve 可以被添加进 Pipeline 的流程末端。具体来说,我们注入恶意Valve对象的步骤包括:(1)获取StandardContext对象;(2)编写恶意的Valve对象;(3)通过StandardContext.getPipeline().addValve()方法添加恶意Valve。
Tomcat Upgrade型内存马
Tomcat Upgrade型内存马主要是利用Processor组件实现字节码的注入。其中,Upgrade型内存马主要是利用Http11Processor在处理请求的过程中对数据进行解析时进行注入。在AbstractProcessorLight的process方法中,会根据当前SocketWrapperBase的状态进行响应的处理。在处理HTTP请求时,对应的Processor为Http11Processor。在处理Upgrade时,会进行三件事情:(1)在Http11Processor的service方法中会检查头部的Connection头中是否为upgrade;(2)根据头部的Upgrade选择出对应的Upgrade对象;(3)调用该对象的accept方法。
进一步追踪Http11Processor,发现为了判断httpUpgradeProtocols是在Tomcat启动时进行的实例化还是在请求时进行的实例化,Tomcat需要知道httpUpgradeProtocols是在什么时候被赋值的。然后一路追踪找到了init方法。然后我们发现在该init方法中Tomcat做了以下几件事情:(1)读取upgradeProtocols列表;(2)调用configureUpgradeProtocol;(3)将对应upgradeProtocol添加到httpUpgradeProtocols的HashMap中。因此我们可以通过反射调用把这个httpUpgradeProtocols添加一项,即可实现Upgrade内存马。
Agent型内存马
Agent型内存马原理:Agent型Java内存马是一种利用Java Agent技术实现的无文件恶意代码,驻留在内存中,通过动态修改JVM中已加载类的字节码实施攻击。
依赖于Java的Instrumentation API,允许攻击者通过agentmain或premain方法注入恶意逻辑。agentmain方法通常通过Attach API动态加载到目标JVM进程中,而premain方法则在JVM启动时加载。攻击者利用com.sun.tools.attach.VirtualMachine类附加到目标进程,无需重启应用即可加载恶意Agent,隐蔽性极高。
Agent型内存马基本流程:攻击者首先通过jps命令或枚举进程列表获取目标Java应用的进程ID。随后,使用Attach API的VirtualMachine.attach(pid)连接到目标JVM,并加载包含恶意代码的Agent JAR包
在Agent中,攻击者通过实现ClassFileTransformer接口,动态修改关键类(如Servlet过滤器、控制器等)的字节码,插入后门逻辑(例如执行任意系统命令)。为实现持久化,恶意代码可能驻留在内存中,并通过定时线程或注册为特定请求处理器维持活跃状态,即使应用重启,若结合其他持久化手段(如修改启动参数),仍可能重新激活。