HttpFirewall

瞭解其機制以及在針對您定義的模式進行測試時使用的 URL 值非常重要。

Servlet 規範定義了 HttpServletRequest 的幾個可透過 getter 方法訪問的屬性,我們可能希望根據這些屬性進行匹配。這些屬性是 contextPathservletPathpathInfoqueryString。Spring Security 只關注保護應用程式中的路徑,因此 contextPath 被忽略。不幸的是,servlet 規範並未明確定義 servletPathpathInfo 的值對於特定請求 URI 包含什麼。例如,URL 的每個路徑段可能包含引數,如 RFC 2396 中定義(當瀏覽器不支援 cookie 並且 jsessionid 引數在分號後附加到 URL 時,您可能已經見過這種情況。然而,RFC 允許這些引數出現在 URL 的任何路徑段中。)規範沒有明確說明這些是否應包含在 servletPathpathInfo 值中,並且不同 servlet 容器之間的行為有所不同。存在一個危險,即當應用程式部署在一個不會從這些值中剝離路徑引數的容器中時,攻擊者可以將其新增到請求的 URL 中,從而導致模式匹配意外成功或失敗。(原始值將在請求離開 FilterChainProxy 後返回,因此仍可供應用程式使用。)傳入 URL 的其他變體也可能存在。例如,它可能包含路徑遍歷序列(如 /../)或多個斜槓(//),這也可能導致模式匹配失敗。一些容器在執行 servlet 對映之前會對其進行標準化,但其他容器則不會。為了防止出現此類問題,FilterChainProxy 使用 HttpFirewall 策略來檢查和包裝請求。預設情況下,未標準化的請求會自動拒絕,並且出於匹配目的,會移除路徑引數和重複的斜槓。(例如,原始請求路徑 /secure;hack=1/somefile.html;hack=2 將作為 /secure/somefile.html 返回。)因此,使用 FilterChainProxy 來管理安全過濾器鏈至關重要。請注意,servletPathpathInfo 值由容器解碼,因此您的應用程式不應包含任何帶有分號的有效路徑,因為這些部分會在匹配時被移除。

如前所述,預設策略是使用 Ant 風格的路徑進行匹配,這對於大多數使用者來說可能是最佳選擇。該策略在類 PathPatternRequestMatcher 中實現,它使用 Spring 的 PathPattern 對連線的 servletPathpathInfo 執行不區分大小寫的模式匹配,並忽略 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 方法列表。預設的有效方法是 DELETEGETHEADOPTIONSPATCHPOSTPUT。如果您的應用程式需要修改有效方法,您可以配置一個自定義的 StrictHttpFirewall bean。以下示例只允許 HTTP GETPOST 方法

只允許 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
}

如果您使用 new MockHttpServletRequest(),它目前將 HTTP 方法建立為空字串 ("")。這是一個無效的 HTTP 方法,並被 Spring Security 拒絕。您可以透過將其替換為 new MockHttpServletRequest("GET", "") 來解決此問題。請參閱 SPR_16851 以瞭解請求改進此問題的缺陷報告。

如果您必須允許任何 HTTP 方法(不推薦),可以使用 StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)。這樣做會完全停用 HTTP 方法的驗證。

StrictHttpFirewall 還會檢查頭部名稱、值和引數名稱。它要求每個字元都有一個定義的碼點,並且不能是控制字元。

此要求可以透過以下方法根據需要放寬或調整

  • StrictHttpFirewall#setAllowedHeaderNames(Predicate)

  • StrictHttpFirewall#setAllowedHeaderValues(Predicate)

  • StrictHttpFirewall#setAllowedParameterNames(Predicate)

引數值也可以透過 setAllowedParameterValues(Predicate) 進行控制。

例如,要關閉此檢查,您可以將 StrictHttpFirewall 與始終返回 truePredicate 例項連線起來

允許任何頭部名稱、頭部值和引數名稱
  • 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

將頭部解析為 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()
}
© . This site is unofficial and not affiliated with VMware.