1. 引言

Spring Framework 5.0 到 5.0.4、4.3 到 4.3.14 及更早版本在 Windows 系统上存在目录/路径遍历安全漏洞。

静态资源配置不当会导致恶意用户访问服务器文件系统。例如,使用 file: 协议提供静态资源时,在 Windows 上可能非法访问文件系统。

Spring 官方已确认该漏洞并在后续版本中修复。此修复能有效防御路径遍历攻击,但也导致部分原有 URL 抛出 org.springframework.security.web.firewall.RequestRejectedException 异常。

本文将深入探讨 RequestRejectedExceptionStrictHttpFirewall 在路径遍历攻击防御中的作用

2. 路径遍历漏洞原理

路径遍历(目录遍历)漏洞允许攻击者非法访问 Web 根目录之外的文件。通过操纵 URL,攻击者可越权获取文档根目录外的文件。

尽管主流 Web 服务器已防御多数此类攻击,但攻击者仍能通过特殊字符(如 ./../)的 URL 编码绕过安全机制。

OWASP 详细描述了此类漏洞及防御方案。

3. Spring Framework 漏洞复现

先复现漏洞再探讨修复方案:

  1. 克隆 Spring MVC 示例项目:

    git clone [email protected]:spring-projects/spring-mvc-showcase.git
    
  2. 修改 pom.xml,使用存在漏洞的 Spring 版本:

    <org.springframework-version>5.0.0.RELEASE</org.springframework-version>
    
  3. 编辑 WebMvcConfig 配置类,使用 file: 协议映射本地目录:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
          .addResourceHandler("/resources/**")
          .addResourceLocations("file:./src/", "/resources/");
    }
    
  4. 启动应用:

    mvn jetty:run
    
  5. 发送恶意请求(%252e%252e%255c..\\ 的双重编码):

    curl 'http://localhost:8080/spring-mvc-showcase/resources/%255c%255c%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/windows/system.ini'
    

危险!响应将返回 Windows 系统文件 system.ini 的内容。

4. Spring Security 的 HttpFirewall 接口

Servlet 规范未明确定义 servletPathpathInfo 的区别,导致不同容器解析行为不一致。例如:

  • Tomcat 9 中,URL http://localhost:8080/api/v1/users/1
     request.getServletPath() // 返回 "/api/v1/users/1"
     request.getPathInfo()     // 返回 null
    

这种不一致可能引发路径遍历攻击。Spring Security 通过 HttpFirewall 接口统一处理恶意 URL,提供两种实现:

4.1. DefaultHttpFirewall

注意:名称有误导性,它并非默认实现。
此实现会尝试规范化 URL,统一不同容器的 servletPathpathInfo。可通过显式声明覆盖默认行为:

@Bean
public HttpFirewall getHttpFirewall() {
    return new DefaultHttpFirewall();
}

但更推荐使用更安全的 StrictHttpFirewall

4.2. StrictHttpFirewall

StrictHttpFirewall 是默认且更严格的实现。
DefaultHttpFirewall 不同,它会直接拒绝非规范化 URL,提供更强保护。同时防御跨站追踪(XST)HTTP 动词篡改等攻击。

支持自定义配置(但不推荐修改默认值):

@Bean
public HttpFirewall getHttpFirewall() {
    StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
    strictHttpFirewall.setAllowSemicolon(true); // 允许分号(不推荐)
    return strictHttpFirewall;
}

当检测到可疑请求时,抛出 RequestRejectedException

5. 依赖配置

添加 Spring Security 和 Web 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>3.1.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.1.5</version>
</dependency>

6. Spring Security 配置

启用 Basic 认证:

@Configuration
public class HttpFirewallConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> 
                auth.requestMatchers("/error").permitAll()
                   .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

application.properties 中设置固定凭据:

spring.security.user.name=user
spring.security.user.password=password

7. 构建受保护的 REST API

创建用户管理接口:

@PostMapping
public ResponseEntity<Response> createUser(@RequestBody User user) {
    userService.saveUser(user);
    Response response = new Response()
      .withTimestamp(System.currentTimeMillis())
      .withCode(HttpStatus.CREATED.value())
      .withMessage("User created successfully");
    URI location = URI.create("/users/" + user.getId());
    return ResponseEntity.created(location).body(response);
}
 
@DeleteMapping("/{userId}")
public ResponseEntity<Response> deleteUser(@PathVariable("userId") String userId) {
    userService.deleteUser(userId);
    return ResponseEntity.ok(new Response(200,
      "The user has been deleted successfully", System.currentTimeMillis()));
}

启动应用:

mvn spring-boot:run

8. 接口测试

正常请求测试

创建用户:

curl -i --user user:password -d @request.json \
     -H "Content-Type: application/json" \
     -H "Accept: application/json" \
     http://localhost:8080/api/v1/users

request.json 内容:

{
    "id":"1",
    "username":"navuluri",
    "email":"[email protected]"
}

响应:

HTTP/1.1 201
Location: /users/1
{
  "code":201,
  "message":"User created successfully",
  "timestamp":1632808055618
}

恶意请求测试

  1. 禁止所有 HTTP 方法
    配置 StrictHttpFirewall

    @Bean
    public HttpFirewall configureFirewall() {
        StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
        strictHttpFirewall.setAllowedHttpMethods(Collections.emptyList());
        return strictHttpFirewall;
    }
    

    再次调用接口将抛出异常:

    org.springframework.security.web.firewall.RequestRejectedException: 
    The request was rejected because the HTTP method "POST" was not included within the list of allowed HTTP methods []
    
  2. 自定义响应状态码(Spring Security 6.1.5+)

    @Bean
    public RequestRejectedHandler requestRejectedHandler() {
       return new HttpStatusRequestRejectedHandler(); // 默认返回 400
    }
    
  3. 允许反斜杠(不推荐)

    strictHttpFirewall.setAllowBackSlash(true);
    strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("GET","POST","DELETE", "OPTIONS"));
    

    调用含反斜杠的 URL:

    curl -i --user user:password -d @request.json \
         -H "Content-Type: application/json" \
         -H "Accept: application/json" \
         http://localhost:8080/api\\v1/users
    

    请求成功!(但这是危险配置)

  4. 恢复严格模式
    删除自定义 @Bean 后测试恶意 URL:

    curl -i --user user:password -d @request.json \
         -H "Content-Type: application/json" \
         -H "Accept: application/json" \
         http://localhost:8080/api/v1//users  # 双斜杠
    
    curl -i --user user:password -d @request.json \
         -H "Content-Type: application/json" \
         -H "Accept: application/json" \
         http://localhost:8080/api/v1\\users  # 反斜杠
    

    均被拦截:

    org.springframework.security.web.firewall.RequestRejectedException: 
    The request was rejected because the URL contained a potentially malicious String "//"
    

9. 总结

本文详解了 Spring Security 如何防御路径遍历攻击:

  • DefaultHttpFirewall 尝试规范化恶意 URL
  • StrictHttpFirewall 直接拒绝可疑请求(推荐使用)
  • 除路径遍历外,StrictHttpFirewall 还防御多种攻击

**生产环境务必使用默认配置的 StrictHttpFirewall**,避免自定义削弱安全性。完整代码见 GitHub


原始标题:Spring Security – Request Rejected Exception