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 框架漏洞
現在,讓我們嘗試複製這個漏洞,在學習如何修復它之前。
首先,讓我們克隆 Spring Framework MVC 示例。稍後,我們將修改 pom.xml 文件,並將現有 Spring Framework 版本替換為具有漏洞的版本。
克隆倉庫:
git clone [email protected]:spring-projects/spring-mvc-showcase.git在克隆的目錄中,編輯 pom.xml 以將 Spring Framework 版本設置為 5.0.0.RELEASE。
<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/%252e%252e%255c/windows/system.ini'. 是 ....\ 的雙編碼形式,. 是 \\. 的雙編碼形式。
危險地,響應將是 Windows 系統文件system.ini的內容。
4. Spring Security HttpFirewall 接口
Servlet 規範並未精確定義 servletPath 和 pathInfo 的區別。因此,Servlet 容器在對這些值的翻譯上存在不一致性。
例如,在 Tomcat 9 上,對於 URL http://localhost:8080/api/v1/users/1,URI /1 被認為是路徑變量。
另一方面,以下代碼返回 /api/v1/users/1:
request.getServletPath()然而,下面的命令返回一個null:
request.getPathInfo()無法從 URI 中區分路徑變量可能導致路徑遍歷/目錄遍歷等潛在攻擊。例如,用户可以通過在 URL 中包含 \\, /../, . 來利用服務器上的系統文件。不幸的是,只有少數 Servlet 容器會規範化這些 URL。
Spring Security 能夠解決這些問題。Spring Security 在不同容器中表現一致,並利用 HttpFirewall 接口規範化這些惡意 URL。該接口有以下兩種實現:
4.1. DefaultHttpFirewall
首先,不要被實現類的名稱所迷惑。換句話説,這並不是默認的 HttpFirewall 實現。
防火牆嘗試對 URL 進行清理或規範化,並對 servletPath 和 pathInfo 在容器之間進行標準化。 此外,我們可以通過顯式聲明一個 @Bean 來覆蓋默認的 HttpFirewall 行為。
@Bean
public HttpFirewall getHttpFirewall() {
return new DefaultHttpFirewall();
}然而,<em >StrictHttpFirewall</em> 提供了一個健壯且安全的實現,並且是推薦的實現。
4.2. StrictHttpFirewall
StrictHttpFirewall** 是 HttpFirewall 的默認且更嚴格的實現。與 DefaultHttpFirewall 不同,StrictHttpFirewall** 會拒絕任何未進行規範化的 URL,從而提供更嚴格的保護。此外,該實現還能夠保護應用程序免受諸如 跨站跟蹤 (XST) 和 HTTP 動詞篡改 等攻擊。
此外,該實現具有可定製性,並且具有合理的默認值。換句話説,我們可以禁用(但不建議)某些功能,例如允許分號作為 URI 的一部分。
@Bean
public HttpFirewall getHttpFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall.setAllowSemicolon(true);
return strictHttpFirewall;
}簡而言之,StrictHttpFirewall 會通過拋出 org.springframework.security.web.firewall.RequestRejectedException 來拒絕可疑請求。
最後,我們使用 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 身份驗證來安全地配置我們的應用程序,通過創建一個配置類來創建 SecurityFilterChain 託管 Bean:
@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("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:run8. 測試 API
現在,讓我們通過 cURL 創建一個 User:
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]"
}Consequently, the response is:
HTTP/1.1 201
Location: /users/1
Content-Type: application/json
{
"code":201,
"message":"User created successfully",
"timestamp":1632808055618
}
現在,讓我們配置我們的 StrictHttpFirewall 以拒絕所有 HTTP 方法的請求:
@Bean
public HttpFirewall configureFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall
.setAllowedHttpMethods(Collections.emptyList());
return strictHttpFirewall;
}
接下來,讓我們再次調用該API。由於我們配置了 StrictHttpFirewall 以限制所有HTTP方法,因此這次我們得到了一個錯誤。
在日誌中,我們有以下異常:
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 v6.1.5 版本起,我們可以使用 RequestRejectedHandler 來自定義在出現 RequestRejectedException 時產生的 HTTP 狀態碼。
@Bean
public RequestRejectedHandler requestRejectedHandler() {
return new HttpStatusRequestRejectedHandler();
}請注意,使用 HttpStatusRequestRejectedHandler 時,默認的 HTTP 狀態碼是 400。但是,我們可以通過在 HttpStatusRequestRejectedHandler 類的構造函數中傳遞狀態碼來自定義它。
現在,讓我們重新配置 StrictHttpFirewall 以允許在 URL 中使用 \\ ,以及在 HTTP GET、POST、DELETE 和 OPTIONS 方法中。
strictHttpFirewall.setAllowBackSlash(true);
strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("GET","POST","DELETE", "OPTIONS")接下來,調用API:
curl -i --user user:password -d @request.json -H "Content-Type: application/json"
-H "Accept: application/json" http://localhost:8080/api\\v1/users以下是響應:
{
"code":201,
"message":"User created successfully",
"timestamp":1632812660569
}最後,我們回到 StrictHttpFirewall 的原始嚴格功能,通過刪除 @Bean 聲明來實現。
接下來,讓我們嘗試使用可疑的 URL 調用的我們的 API:
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\\users立即,以上所有請求均以錯誤日誌失敗:
org.springframework.security.web.firewall.RequestRejectedException:
The request was rejected because the URL contained a potentially malicious String "//"9. 結論
本文介紹了 Spring Security 防禦惡意 URL 的機制,這些 URL 可能導致路徑遍歷/目錄遍歷攻擊。
DefaultHttpFirewall 嘗試規範化惡意 URL。然而,StrictHttpFirewall 會通過拋出 RequestRejectedException 來拒絕這些請求。 StrictHttpFirewall 不僅保護我們免受路徑遍歷攻擊,還保護我們免受其他攻擊。 因此,強烈建議同時使用 StrictHttpFirewall 及其默認配置。