Spring Boot CloudFoundry Actuator 认证绕过漏洞 (CVE-2026-22733) 教学文档
1. 漏洞信息
漏洞标识:CVE-2026-22733
漏洞类型:认证绕过
影响组件:Spring Boot CloudFoundry Actuator
漏洞等级:高危
2. 受影响版本
漏洞影响多个Spring Boot主版本系列,具体如下:
- Spring Boot 4.x 系列:4.0.0 至 4.0.3
- Spring Boot 3.x 系列:
- 3.5.0 至 3.5.11
- 3.4.0 至 3.4.14
- 3.3.0 至 3.3.17
- Spring Boot 2.x 系列:2.7.0 至 2.7.31
- 注意:2.7.9 版本有部分改进,但漏洞依然存在,直到 2.7.32 才完全修复。
3. 漏洞描述
3.1 根本原因
此漏洞的根本原因是 Spring CloudFoundry Actuator 自动配置中的安全配置与 Spring MVC 的请求处理机制(HandlerMapping)之间存在安全缺陷,导致配合不当,形成认证绕过。
3.2 漏洞触发条件
- 应用环境:应用必须运行在(或配置为运行在)CloudFoundry 平台环境中,这通常通过设置
spring.main.cloud-platform=CLOUD_FOUNDRY属性来激活 CloudFoundry 相关的自动配置。 - 配置:必须启用了 Spring Security 和 Actuator,并且 CloudFoundry Actuator 功能被启用 (
management.cloudfoundry.enabled=true, 通常由上述平台条件触发)。 - 代码:存在将自定义控制器(Controller)映射到
/cloudfoundryapplication/**路径下的情况。
3.3 漏洞机制
漏洞的形成涉及三个关键环节的连锁反应:
环节一:Spring Security 的过度忽略
- CloudFoundryActuatorAutoConfiguration 会自动配置一个
IgnoredCloudFoundryPathsWebSecurityCustomizer组件。 - 在受影响版本(如 2.7.0-2.7.8)中,此组件会调用
web.ignoring().requestMatchers(new AntPathRequestMatcher(“/cloudfoundryapplication/**“))。 - 后果:
web.ignoring()会让 Spring Security 的安全过滤器链(Security FilterChain)完全忽略对/cloudfoundryapplication/**路径下所有请求的安全检查,包括认证和授权。请求会直接“穿过”安全层。
环节二:CloudFoundry 的 HandlerMapping 未完全覆盖
- 对于
/cloudfoundryapplication/**路径的请求,Spring MVC 会首先交由CloudFoundryWebEndpointServletHandlerMapping尝试处理。 - 问题:这个 HandlerMapping 只会处理真正在 CloudFoundry Actuator 中注册过的端点(例如
/cloudfoundryapplication/health)。对于该路径下的、未在 CloudFoundry 注册的端点(例如自定义的/cloudfoundryapplication/admin),此 HandlerMapping 会返回null,表示无法处理。 - 后果:请求会继续向下传递给其他 HandlerMapping。
环节三:请求落入自定义控制器
- 当
RequestMappingHandlerMapping(负责处理普通@Controller和@RestController)收到请求时,它会成功匹配到开发者自定义的、映射在/cloudfoundryapplication/admin的控制器方法。 - 后果:由于请求已在环节一被 Spring Security 忽略,因此无需任何认证即可直接执行业务逻辑,返回敏感数据,造成认证绕过。
总结漏洞链:
Spring Security 完全忽略路径 -> CloudFoundry专属HandlerMapping不处理未知端点 -> 普通HandlerMapping处理自定义端点 -> 认证被绕过。
4. 环境搭建与验证
4.1 关键依赖配置 (Maven)
创建一个 Spring Boot 2.7.8 的项目(用于复现原始漏洞),pom.xml 关键依赖如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
4.2 关键应用配置
application.properties 中必须包含以下配置以触发漏洞:
server.port=8080
# 关键配置:模拟或指定应用运行在CloudFoundry平台,激活漏洞相关自动配置
spring.main.cloud-platform=CLOUD_FOUNDRY
# 启用CloudFoundry管理端点(通常由上一条配置自动触发)
management.cloudfoundry.enabled=true
# 暴露所有Actuator端点
management.endpoints.web.exposure.include=*
4.3 安全配置(存在漏洞的配置)
创建一个安全配置类,意图是保护 /cloudfoundryapplication/** 路径,但由于自动配置的干扰,此配置不会生效。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/actuator/health").permitAll()
.antMatchers("/cloudfoundryapplication/**").authenticated() // 此配置将被覆盖/忽略
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().disable();
return http.build();
}
// ... UserDetailsService 配置 ...
}
4.4 漏洞触发控制器
创建映射到漏洞路径的自定义控制器:
@RestController
@RequestMapping("/cloudfoundryapplication")
public class CloudFoundryAppController {
@GetMapping("/admin")
public String admin() {
return "ADMIN_ENDPOINT_EXPOSED - This should require authentication!";
}
@GetMapping("/secrets")
public String secrets() {
return "SECRET_DATA: database_password=root123, api_key=sk-secret-key";
}
}
4.5 启动验证
启动应用后,在日志中查找关键信息,确认漏洞条件成立:
WARN o.s.s.c.a.web.builders.WebSecurity : You are asking Spring Security to ignore Ant [pattern='/cloudfoundryapplication/**']. This is not recommended
INFO o.s.s.web.DefaultSecurityFilterChain : Will not secure Ant [pattern='/cloudfoundryapplication/**']
出现以上警告和信息日志,表明 CloudFoundry 自动配置已生效,并强制 Spring Security 忽略了对该路径的保护。
4.6 漏洞复现
直接访问自定义端点,无需提供任何认证凭证(如Basic Auth的用户名密码):
GET http://localhost:8080/cloudfoundryapplication/admin
GET http://localhost:8080/cloudfoundryapplication/secrets
预期结果(错误):应返回 200 OK 及控制器返回的敏感信息。
正确结果:应返回 401 Unauthorized。
作为对比,访问一个配置在非 /cloudfoundryapplication 路径下的受保护端点(如 /normal),则会正常要求认证。
5. 漏洞深度分析
5.1 自动配置类分析
在 spring-boot-actuator-autoconfigure-2.7.8.jar 中,CloudFoundryActuatorAutoConfiguration 包含一个内部类:
@Order(SecurityProperties.IGNORED_ORDER)
static class IgnoredCloudFoundryPathsWebSecurityCustomizer implements WebSecurityCustomizer {
@Override
public void customize(WebSecurity web) {
// 漏洞核心:粗粒度地忽略了整个路径
web.ignoring().requestMatchers(new AntPathRequestMatcher("/cloudfoundryapplication/**"));
}
}
@Order(SecurityProperties.IGNORED_ORDER) 保证了该配置器以高优先级(低order值)执行,覆盖了开发者在 SecurityConfig 中的配置。
5.2 请求处理流程剖析
- 请求进入:
GET /cloudfoundryapplication/admin - 安全过滤:由于
web.ignoring(),Spring Security 的所有Filter跳过此请求。 - Handler 解析:
a.DispatcherServlet询问CloudFoundryWebEndpointServletHandlerMapping能否处理 “/admin”。
b. 该 HandlerMapping 检查其注册表,发现 “/admin” 不是一个已注册的 CloudFoundry 端点,返回null。
c.DispatcherServlet继续询问RequestMappingHandlerMapping。
d.RequestMappingHandlerMapping成功匹配到CloudFoundryAppController.admin()方法,并返回对应的处理器。 - 执行业务逻辑:控制器方法执行,返回数据。
- 响应返回:HTTP 200 及敏感数据被返回给未认证的客户端。
6. 修复方案与演变
6.1 中间修复 (Spring Boot 2.7.9 - 2.7.18)
Spring 团队在 2.7.9 版本中改进了 IgnoredCloudFoundryPathsWebSecurityCustomizer 的逻辑:
@Override
public void customize(WebSecurity web) {
List<RequestMatcher> requestMatchers = new ArrayList<>();
// 关键变化:只忽略已注册的CloudFoundry端点路径,而非整个 `/cloudfoundryapplication/**`
this.pathMappedEndpoints.getAllPaths()
.forEach((path) -> requestMatchers.add(new AntPathRequestMatcher(path + "/**")));
requestMatchers.add(new AntPathRequestMatcher(BASE_PATH)); // BASE_PATH = “/cloudfoundryapplication“
requestMatchers.add(new AntPathRequestMatcher(BASE_PATH + "/"));
web.ignoring().requestMatchers(new OrRequestMatcher(requestMatchers));
}
效果与局限:此修复缩小了 Spring Security 的忽略范围。然而,由于 CloudFoundryWebEndpointServletHandlerMapping 仍然没有处理未知路径的机制,自定义控制器的请求依然会 “fall through“ 并被处理,漏洞仍然存在,只是利用条件稍严格。
6.2 完整修复 (Spring Boot 2.7.32+, 3.5.12+, 4.0.4+)
真正的修复是在 CloudFoundryWebEndpointServletHandlerMapping 层级增加了“兜底处理”(catch-all)机制。
-
在 HandlerMapping 初始化时注册兜底处理器:
@Override protected void initHandlerMethods() { super.initHandlerMethods(); // 注册一个匹配 /** 的处理器,用于拦截所有未明确注册的路径 registerCatchAllMapping(HttpStatus.FORBIDDEN); } -
兜底处理器的实现:
protected void registerCatchAllMapping(HttpStatus responseStatus) { String subPath = this.endpointMapping.createSubPath("/**"); registerMapping( RequestMappingInfo.paths(subPath).options(this.builderConfig).build(), new CatchAllHandler(responseStatus), // 返回指定状态码的处理器 this.catchAllMethod ); } private static final class CatchAllHandler { private final HttpStatus responseStatus; void handle(HttpServletResponse response) { response.setStatus(this.responseStatus.value()); // 例如返回 403 } }
效果:现在,任何访问 /cloudfoundryapplication/** 下未在 CloudFoundry 注册的路径的请求,都会被 CatchAllHandler 拦截,并直接返回 403 Forbidden,而不会再传递给下游的 RequestMappingHandlerMapping。至此,认证绕过漏洞被彻底堵塞。
7. 修复与缓解建议
7.1 根本解决(推荐)
- 升级Spring Boot:将应用升级到已修复的版本。
- Spring Boot 2.7.x 用户升级至 2.7.32 或更高。
- Spring Boot 3.3.x 用户升级至 3.3.18 或更高。
- Spring Boot 3.4.x 用户升级至 3.4.14 或更高。
- Spring Boot 3.5.x 用户升级至 3.5.12 或更高。
- Spring Boot 4.0.x 用户升级至 4.0.4 或更高。
7.2 临时缓解措施
如果无法立即升级,可考虑以下方案:
-
方案A:修改应用代码
绝对不要将自定义的控制器端点映射到/cloudfoundryapplication/路径下。为应用接口使用独立的、无冲突的上下文路径,例如/api/**或/app/**。 -
方案B:添加安全补丁
在安全配置中,手动添加一个高优先级的过滤器(Filter)来拦截/cloudfoundryapplication/*路径下的未知请求。示例如下:@Bean public FilterRegistrationBean<CatchAllFilter> catchAllFilter() { FilterRegistrationBean<CatchAllFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new CatchAllFilter()); registration.addUrlPatterns("/cloudfoundryapplication/*"); registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设置为最高优先级 return registration; } public static class CatchAllFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String path = req.getRequestURI().substring(req.getContextPath().length()); // 此处可添加逻辑,判断是否为CloudFoundry已知端点,否则拦截 // 简单示例:对于非根路径的未知请求直接拒绝 if (!path.equals("/cloudfoundryapplication") && !isKnownCloudFoundryPath(path)) { ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_FORBIDDEN); return; } chain.doFilter(request, response); } private boolean isKnownCloudFoundryPath(String path) { // 实现逻辑,检查path是否在已知的CloudFoundry Actuator端点列表中 return false; } }
8. 参考资料
- GitHub Advisory: GHSA-mgvc-8q2h-5pgc
- NVD: CVE-2026-22733
- Spring Boot Issue #49645
- 修复 Commit: 01fbede