最新の安定バージョンについては、Spring Security 6.3.1 を使用してください!

HttpFirewall

定義したパターンに対してテストするときは、メカニズムとは何か、どの URL 値が使用されるかを理解することが重要です。

サーブレット仕様では、getter メソッドを介してアクセスできる HttpServletRequest のいくつかのプロパティが定義されており、これらのプロパティと照合する必要があります。これらは contextPathservletPathpathInfoqueryString です。Spring Security は、アプリケーション内のパスの保護のみに関心があるため、contextPath は無視されます。残念ながら、サーブレットの仕様では、特定のリクエスト URI に対して servletPath と pathInfo の値に何が含まれるかを正確に定義していません。例: URL の各パスセグメントには、RFC 2396 [IETF] (英語) [ 1 ] で定義されているパラメーターを含めることができます。仕様では、これらを servletPath 値と pathInfo 値に含める必要があるかどうかが明確に規定されておらず、動作はサーブレットコンテナーごとに異なります。これらの値からパスパラメーターを削除しないコンテナーにアプリケーションをデプロイすると、攻撃者がパターンマッチを成功または予期せず失敗させるために、リクエストされた URL に追加する可能性があるという危険性があります。[2 ]。受信 URL の他のバリエーションも可能です。例: パストラバーサルシーケンス(/../ など)または複数のスラッシュ(//)が含まれている可能性があり、パターン一致が失敗する可能性もあります。一部のコンテナーは、サーブレットマッピングを実行する前にこれらを正規化しますが、そうでないコンテナーもあります。このような課題から保護するために、FilterChainProxy は HttpFirewall 戦略を使用して、リクエストをチェックしてラップします。正規化されていないリクエストはデフォルトで自動的に拒否され、パスパラメーターと重複するスラッシュは照合のために削除されます。[3 ]。セキュリティフィルターチェーンの管理には FilterChainProxy を使用することが不可欠です。servletPath と pathInfo の値はコンテナーによってデコードされるため、これらの部分は照合のために削除されるため、アプリケーションにはセミコロンを含む有効なパスを含めないでください。

前述のように、デフォルトの戦略はマッチングに Ant スタイルのパスを使用することであり、これはほとんどのユーザーにとって最良の選択である可能性が高いです。この戦略は、AntPathRequestMatcher に実装され、Spring の AntPathMatcher を使用して、queryString を無視して、連結された servletPath および pathInfo に対してパターンの大文字と小文字を区別しない一致を実行します。

何らかの理由で、より強力なマッチング戦略が必要な場合は、正規表現を使用できます。戦略の実装は RegexRequestMatcher です。詳細については、このクラスの Javadoc を参照してください。

実際には、アプリケーションへのアクセスを制御するために、サービスレイヤーでメソッドセキュリティを使用することをお勧めします。Web アプリケーションレベルで定義されたセキュリティ制約の使用に完全に依存しないでください。URL が変更され、アプリケーションがサポートする可能性のあるすべての URL と、リクエストの操作方法を考慮することは困難です。理解しやすいいくつかの単純な ant パスの使用に制限する必要があります。最後に定義されたキャッチオールワイルドカード(/ または)があり、アクセスを拒否する場合は、常に「デフォルトで拒否」アプローチを使用するようにしてください。

サービス層で定義されたセキュリティは、はるかに堅牢であり、迂回するのが難しいため、Spring Security のメソッドセキュリティオプションを常に利用する必要があります。

HttpFirewall は、HTTP レスポンスヘッダーの改行文字を拒否することにより、HTTP レスポンスの分割 [OWASP] (英語) も防止します。

デフォルトでは、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
}

StrictHttpFirewall は、クロスサイトトレース (XST) [OWASP] (英語) および HTTP 動詞の改ざん [OWASP] (英語) に対する保護が許可されている有効な HTTP メソッドの許可リストを提供します。デフォルトの有効なメソッドは、"DELETE"、"GET"、"HEAD"、"OPTIONS"、"PATCH"、"POST"、"PUT" です。アプリケーションで有効なメソッドを変更する必要がある場合は、カスタム StrictHttpFirewall Bean を構成できます。例: 以下は、HTTP の "GET" および "POST" メソッドのみを許可します。

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,HEAD"/>

<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) で制御できます。

例: このチェックをオフにするには、次のように、常に true を返す Predicate を使用して StrictHttpFirewall を接続できます。

任意のヘッダー名、ヘッダー値、パラメーター名を許可する
  • 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 は、ISO-8859-1 文字セットに含まれていない文字を含む User-Agent を使用します。このため、一部のアプリケーションサーバーは、この値を 2 つの別個の文字に解析します。後者は、未定義の文字です。

以下に示すように、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()
}

1. ブラウザーで Cookie がサポートされておらず、セミコロンの後に URL に jsessionid パラメーターが追加されている場合、おそらくこれを確認しているでしょう。ただし、RFC では、URL の任意のパスセグメントにこれらのパラメーターが存在することを許可しています
2. リクエストが FilterChainProxy を離れると、元の値が返されるため、アプリケーションは引き続き使用できます。
3. たとえば、元のリクエストパス /secure;hack=1/somefile.html;hack=2 は /secure/somefile.html として返されます。