HttpFirewall
理解該機制以及在對照您定義的模式進行測試時使用的 URL 值是什麼,這一點很重要。
Servlet 規範定義了 HttpServletRequest
的幾個屬性,這些屬性可以透過 getter 方法訪問,我們可能需要對照它們進行匹配。這些屬性是 contextPath
、servletPath
、pathInfo
和 queryString
。Spring Security 只關注保護應用程式內部的路徑,因此 contextPath
會被忽略。不幸的是,Servlet 規範並未精確定義 servletPath
和 pathInfo
對於特定請求 URI 所包含的值。例如,URL 的每個路徑段都可以包含引數,如 RFC 2396 中所定義(當瀏覽器不支援 Cookie 並且 jsessionid
引數被附加到 URL 中分號之後時,您可能見過這種情況。但是,RFC 允許這些引數出現在 URL 的任何路徑段中。)規範沒有明確說明這些引數是否應包含在 servletPath
和 pathInfo
值中,而且不同 Servlet 容器的行為也各不相同。存在這樣一種危險:當應用程式部署在不會從這些值中剝離路徑引數的容器中時,攻擊者可以將其新增到請求的 URL 中,從而導致模式匹配意外成功或失敗。(原始值在請求離開 FilterChainProxy
後會返回,因此仍然可供應用程式使用。)傳入 URL 的其他變體也可能發生。例如,它可能包含路徑遍歷序列(如 /../
)或多個正斜槓(//
),這也可能導致模式匹配失敗。有些容器在執行 Servlet 對映之前會對其進行標準化處理,但有些則不會。為了防範此類問題,FilterChainProxy
使用 HttpFirewall
策略來檢查幷包裝請求。預設情況下,非標準化的請求會自動拒絕,並且為了匹配目的,會移除路徑引數和重複的正斜槓。(例如,原始請求路徑 /secure;hack=1/somefile.html;hack=2
會被處理為 /secure/somefile.html
。)因此,使用 FilterChainProxy
來管理安全過濾器鏈至關重要。請注意,servletPath
和 pathInfo
值由容器解碼,因此您的應用程式不應包含任何包含分號的有效路徑,因為這些部分會為了匹配目的而被移除。
如前所述,預設的匹配策略是使用 Ant 風格的路徑,這對於大多數使用者來說可能是最好的選擇。該策略在 AntPathRequestMatcher
類中實現,它使用 Spring 的 AntPathMatcher
對串聯的 servletPath
和 pathInfo
進行模式的忽略大小寫匹配,並忽略 queryString
。
如果您需要更強大的匹配策略,可以使用正則表示式。此時,策略實現是 RegexRequestMatcher
。有關更多資訊,請參閱 RegexRequestMatcher
的 Javadoc。
實際上,我們建議您在服務層使用方法安全來控制對應用程式的訪問,而不是完全依賴於 Web 應用程式級別定義的安全約束。URL 會變化,而且很難考慮到應用程式可能支援的所有可能 URL 以及請求如何被操縱。您應該限制自己只使用一些簡單易懂的 Ant 路徑。始終嘗試使用“預設拒絕”方法,即最後定義一個全匹配的萬用字元(/
或 )來拒絕訪問。
在服務層定義的安全性更加健壯,也更難繞過,因此您應該始終利用 Spring Security 的方法安全選項。
HttpFirewall
還透過拒絕 HTTP 響應頭中的換行符來阻止 HTTP 響應拆分。
預設情況下,使用 StrictHttpFirewall
實現。此實現會拒絕看起來惡意的請求。如果它對您的需求來說過於嚴格,您可以自定義拒絕哪種型別的請求。但是,重要的是您在這樣做時要了解這可能會使您的應用程式面臨攻擊。例如,如果您希望使用 Spring MVC 的矩陣變數,您可以使用以下配置:
-
Java
-
XML
-
Kotlin
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
<b:bean id="httpFirewall"
class="org.springframework.security.web.firewall.StrictHttpFirewall"
p:allowSemicolon="true"/>
<http-firewall ref="httpFirewall"/>
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
firewall.setAllowSemicolon(true)
return firewall
}
為了防範 跨站追蹤 (XST) 和 HTTP 動詞篡改,StrictHttpFirewall
提供了一個允許的有效 HTTP 方法列表。預設的有效方法是 DELETE
、GET
、HEAD
、OPTIONS
、PATCH
、POST
和 PUT
。如果您的應用程式需要修改有效方法,可以配置自定義的 StrictHttpFirewall
bean。以下示例僅允許 HTTP GET
和 POST
方法:
-
Java
-
XML
-
Kotlin
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
return firewall;
}
<b:bean id="httpFirewall"
class="org.springframework.security.web.firewall.StrictHttpFirewall"
p:allowedHttpMethods="GET,POST"/>
<http-firewall ref="httpFirewall"/>
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
firewall.setAllowedHttpMethods(listOf("GET", "POST"))
return firewall
}
如果您使用 |
如果您必須允許任何 HTTP 方法(不推薦),可以使用 StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)
。這樣做會完全停用對 HTTP 方法的驗證。
StrictHttpFirewall
還會檢查請求頭名稱、值和引數名稱。它要求每個字元都有定義的碼點,且不是控制字元。
可以使用以下方法根據需要放寬或調整此要求:
-
StrictHttpFirewall#setAllowedHeaderNames(Predicate)
-
StrictHttpFirewall#setAllowedHeaderValues(Predicate)
-
StrictHttpFirewall#setAllowedParameterNames(Predicate)
引數值也可以透過 |
例如,要關閉此檢查,您可以將 StrictHttpFirewall
配置為始終返回 true
的 Predicate
例項:
-
Java
-
Kotlin
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHeaderNames((header) -> true);
firewall.setAllowedHeaderValues((header) -> true);
firewall.setAllowedParameterNames((parameter) -> true);
return firewall;
}
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
firewall.setAllowedHeaderNames { true }
firewall.setAllowedHeaderValues { true }
firewall.setAllowedParameterNames { true }
return firewall
}
或者,可能存在您需要允許的特定值。
例如,iPhone Xʀ 使用的 User-Agent
包含一個不在 ISO-8859-1 字元集中的字元。由於此原因,一些應用伺服器會將此值解析為兩個單獨的字元,其中後者是未定義字元。
您可以使用 setAllowedHeaderValues
方法來解決此問題:
-
Java
-
Kotlin
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
Pattern userAgent = ...;
firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
return firewall;
}
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*")
val userAgent = Pattern.compile(...)
firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }
return firewall
}
對於請求頭值,您可以考慮在驗證時將其解析為 UTF-8:
-
Java
-
Kotlin
firewall.setAllowedHeaderValues((header) -> {
String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
return allowed.matcher(parsed).matches();
});
firewall.setAllowedHeaderValues {
val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
return allowed.matcher(parsed).matches()
}