1. 簡介
Spring Framework 5.0 到 5.0.4,4.3 到 4.3.14 以及其他較舊版本,在 Windows 系統上存在目錄遍歷安全漏洞。
不當配置靜態資源允許惡意用户訪問服務器的文件系統。例如,使用 file: 協議提供靜態資源,會導致在 Windows 系統上非法訪問文件系統
Spring Framework 承認了 該漏洞並在後續版本中修復了它。
因此,此修復程序可以保護應用程序免受目錄遍歷攻擊。但是,與此修復程序一起,一些較早的 URL 現在會拋出 org.springframework.security.web.firewall.RequestRejectedException異常
最後,在本教程中,讓我們學習關於 org.springframework.security.web.firewall.RequestRejectedException和StrictHttpFirewall在目錄遍歷攻擊的背景下
2. 路徑遍歷漏洞
路徑遍歷或目錄遍歷漏洞允許非法訪問位於 Web 文檔根目錄之外的文件。例如,通過操縱 URL 可以未經授權地訪問不在文檔根目錄之外的文件。
雖然大多數最新和流行的 Web 服務器已經緩解了這些攻擊,但攻擊者仍然可以使用 URL 編碼特殊字符(如“./”、“../”)來規避 Web 服務器的安全並獲得非法訪問。
此外,OWASP 討論了路徑遍歷漏洞以及如何解決它們。
3. Spring Framework 漏洞
現在,讓我們嘗試複製這個漏洞,在我們學習如何修復它之前。
首先,讓我們克隆 Spring Framework MVC 示例。 稍後,我們將修改 pom.xml 並用一個易受攻擊的版本替換現有的 Spring Framework 版本。
克隆存儲庫:
git clone [email protected]:spring-projects/spring-mvc-showcase.git
在克隆的目錄中,編輯 pom.xml 以包含 5.0.0.RELEASE 作為 Spring Framework 版本:
<org.springframework-version>5.0.0.RELEASE</org.springframework-version>
接下來,編輯 web 配置類 WebMvcConfig 並修改 addResourceHandlers 方法,使用 file: 將資源映射到本地文件目錄:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/resources/**")
.addResourceLocations("file:./src/", "/resources/");
}
稍後,構建工件並運行我們的 web 應用:
mvn jetty:run
現在,當服務器啓動時,調用 URL:
curl 'http://localhost:8080/spring-mvc-showcase/resources/%255c%255c%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/windows/system.ini'
%252e%252e%255c 是對 ..\ 的雙編碼,%255c%255c 是對 \\. 的雙編碼。
令人可悲的是,響應將是 Windows 系統文件 system.ini 的內容。
4. Spring Security HttpFirewall 接口
Servlet 規範 Servlet 規範 並不能精確地定義 servletPath 和 pathInfo 之間的區別。因此,Servlet 容器在翻譯這些值時存在不一致性。
例如,在 Tomcat 9 上,對於 URL http://localhost:8080/api/v1/users/1,URI /1 被認為是路徑變量。
另一方面,以下代碼返回 null:
request.getServletPath()
無法區分路徑變量和 URI 導致潛在攻擊,如路徑遍歷/目錄遍歷攻擊。例如,用户可以通過包含 URL 中的 \\, , /../, . 來利用系統文件。不幸的是,只有一些 Servlet 容器會規範化這些 URL。
Spring Security 登場了。Spring Security 在容器中保持一致的行為,並規範化這些惡意 URL,利用 HttpFirewall 接口。該接口有 2 個實現:
4.1. DefaultHttpFirewall
HttpFirewall implementation.">首先,不要被實現類的名稱所迷惑。換句話説,這不是默認的 HttpFirewall 實現。
該防火牆嘗試規範化或標準化 URL,並對 servletPath 和 pathInfo 在容器中保持一致。 此外,我們可以通過顯式聲明一個 @Bean 來覆蓋默認的 HttpFirewall 行為:
@Bean
public HttpFirewall getHttpFirewall() {
return new DefaultHttpFirewall();
}
但是,StrictHttpFirewall 提供了一個健壯且安全的實現,並且是推薦的實現。
4.2. StrictHttpFirewall
HttpFirewall.">StrictHttpFirewall 是 HttpFirewall 的默認且更嚴格的實現。 另一方面,與 DefaultHttpFirewall 不同,StrictHttpFirewall 會拒絕任何未規範化的 URL,從而提供更嚴格的保護。 此外,該實現可以保護應用程序免受跨站點跟蹤 (XST) 和 HTTP 謂詞篡改等其他攻擊:跨站點跟蹤 (XST) 和 HTTP 謂詞篡改。
此外,該實現是可定製的,並且具有合理的默認值。 換句話説,我們可以禁用(不推薦)一些功能,例如允許在 URI 中作為一部分使用分號:
@Bean
public HttpFirewall getHttpFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall.setAllowSemicolon(true);
return strictHttpFirewall;
}
簡而言之,StrictHttpFirewall 會拒絕可疑請求,並返回 org.springframework.security.web.firewall.RequestRejectedException。
StrictHttpFirewall in action.">最後,讓我們使用 Spring REST 和 Spring Security 開發一個具有用户 CRUD 操作的“用户管理”應用程序,並查看 StrictHttpFirewall 在行動中的情況。
5. 依賴項
讓我們聲明 Spring Security 和 Spring 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 Authentication 來安全地保護我們的應用程序,通過創建一個配置類來創建 SecurityFilterChain 注入:
@Configuration
public class HttpFirewallConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
authorizationManagerRequestMatcherRegistry.requestMatchers("/error").permitAll().anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
默認情況下,Spring Security 提供一個默認密碼,每次重啓都會更改。因此,我們將在 application.properties 中創建一個默認用户名和密碼:
spring.security.user.name=user
spring.security.user.password=password
因此,我們將使用這些憑據訪問我們的受保護的 REST API。
7. 構建安全的 REST API
現在,讓我們構建我們的用户管理 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("用户創建成功");
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,
"用户已成功刪除", System.currentTimeMillis()));
}
現在,讓我們構建並運行應用程序:
mvn spring-boot:run
8. Testing the APIs
Now, let’s start by creating a User using cURL:
curl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api/v1/usersHere is a request.json:
{
"id":"1",
"username":"navuluri",
"email":"[email protected]"
}Consequently, the response is:
HTTP/1.1 201
Location: /users/1
Content-Type: application/json
{
"code":201,
"message":"User created successfully",
"timestamp":1632808055618
}
Now, let’s configure our StrictHttpFirewallto deny requests from all the HTTP methods:
@Bean
public HttpFirewall configureFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall
.setAllowedHttpMethods(Collections.emptyList());
return strictHttpFirewall;
}
Next, let’s invoke the API again. Since we configured StrictHttpFirewall to restrict all the HTTP methods, this time, we get an error.
In the logs, we have this exception:
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 []Since Spring Security v6.1.5, we can use RequestRejectedHandler to customize the HTTP Status when there is a RequestRejectedException:
@Bean
public RequestRejectedHandler requestRejectedHandler() {
return new HttpStatusRequestRejectedHandler();
}Note that the default HTTP status code when using a HttpStatusRequestRejectedHandler is 400. However, we can customize this by passing a status code in the constructor of the HttpStatusRequestRejectedHandler class.
Now, let’s reconfigure the StrictHttpFirewall to allow \\ in the URL and HTTP GET, POST, DELETE, and OPTIONS methods:
strictHttpFirewall.setAllowBackSlash(true);
strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("GET","POST","DELETE", "OPTIONS")Next, invoke the API:
curl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api\\/v1/usersAnd here we have a response:
{
"code":201,
"message":"User created successfully",
"timestamp":1632812660569
}Finally, let’s revert to the original strict functionality of StrictHttpFirewall by deleting the @Bean declaration.
Next, let’s try to invoke our API with suspicious URLs:
curl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api\\/v1\\/userscurl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api\\/v1\\/usersStraightaway, all the above requests fail with error log:
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 []
9. 結論
本文介紹了 Spring Security 防禦惡意 URL 的方法,這些 URL 可能會導致 Path Traversal/Directory Traversal 攻擊。
DefaultHttpFirewall 嘗試規範化惡意 URL。但是,StrictHttpFirewall 通過拋出 RequestRejectedException 來拒絕帶有惡意 URL 的請求。 此外,StrictHttpFirewall 還可以保護我們免受其他攻擊。 因此,強烈建議同時使用 StrictHttpFirewall 及其默認配置。