1. 引言
Spring Framework 5.0 到 5.0.4、4.3 到 4.3.14 及更早版本在 Windows 系统上存在目录/路径遍历安全漏洞。
静态资源配置不当会导致恶意用户访问服务器文件系统。例如,使用 file:
协议提供静态资源时,在 Windows 上可能非法访问文件系统。
Spring 官方已确认该漏洞并在后续版本中修复。此修复能有效防御路径遍历攻击,但也导致部分原有 URL 抛出 org.springframework.security.web.firewall.RequestRejectedException
异常。
本文将深入探讨 RequestRejectedException
和 StrictHttpFirewall
在路径遍历攻击防御中的作用。
2. 路径遍历漏洞原理
路径遍历(目录遍历)漏洞允许攻击者非法访问 Web 根目录之外的文件。通过操纵 URL,攻击者可越权获取文档根目录外的文件。
尽管主流 Web 服务器已防御多数此类攻击,但攻击者仍能通过特殊字符(如 ./
、../
)的 URL 编码绕过安全机制。
OWASP 详细描述了此类漏洞及防御方案。
3. Spring Framework 漏洞复现
先复现漏洞再探讨修复方案:
克隆 Spring MVC 示例项目:
git clone [email protected]:spring-projects/spring-mvc-showcase.git
修改
pom.xml
,使用存在漏洞的 Spring 版本:<org.springframework-version>5.0.0.RELEASE</org.springframework-version>
编辑
WebMvcConfig
配置类,使用file:
协议映射本地目录:@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry .addResourceHandler("/resources/**") .addResourceLocations("file:./src/", "/resources/"); }
启动应用:
mvn jetty:run
发送恶意请求(
%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 规范未明确定义 servletPath
和 pathInfo
的区别,导致不同容器解析行为不一致。例如:
- 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,统一不同容器的 servletPath
和 pathInfo
。可通过显式声明覆盖默认行为:
@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
}
恶意请求测试
禁止所有 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 []
自定义响应状态码(Spring Security 6.1.5+)
@Bean public RequestRejectedHandler requestRejectedHandler() { return new HttpStatusRequestRejectedHandler(); // 默认返回 400 }
允许反斜杠(不推荐)
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
请求成功!(但这是危险配置)
恢复严格模式
删除自定义@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
尝试规范化恶意 URLStrictHttpFirewall
直接拒绝可疑请求(推荐使用)- 除路径遍历外,
StrictHttpFirewall
还防御多种攻击
**生产环境务必使用默认配置的 StrictHttpFirewall
**,避免自定义削弱安全性。完整代码见 GitHub。