HttpFirewall
定義したパターンに対してテストを行う場合、どのようなメカニズムで、どのような URL 値が使われるのかを理解することが重要です。
サーブレット仕様では、getter メソッドを介してアクセス可能であり、照合する可能性のある HttpServletRequest
のいくつかのプロパティが定義されています。これらは contextPath
、servletPath
、pathInfo
、queryString
です。Spring Security は、アプリケーション内のパスの保護のみに関心があるため、contextPath
は無視されます。残念ながら、サーブレットの仕様では、特定のリクエスト URI に対して servletPath
と pathInfo
の値に何が含まれるかを正確に定義していません。例: URL の各パスセグメントには、RFC 2396 [IETF] (英語) で定義されているパラメーターが含まれている場合があります(ブラウザーが Cookie をサポートしておらず、jsessionid
パラメーターがセミコロンの後に URL に追加されている場合にこれを確認した可能性があります。ただし、RFC ではこれらのパラメーターは、URL の任意のパスセグメントにあります)仕様では、これらを servletPath
値と pathInfo
値に含める必要があるかどうかが明確に規定されておらず、動作はサーブレットコンテナーごとに異なります。これらの値からパスパラメーターを削除しないコンテナーにアプリケーションをデプロイすると、攻撃者がリクエストされた URL に追加して、パターン一致が予期せず成功または失敗する可能性があるという危険性があります。(リクエストが FilterChainProxy
を離れると元の値が返されるため、アプリケーションで引き続き使用できます)受信 URL の他のバリエーションも可能です。例: パストラバーサルシーケンス(/../
など)または複数のスラッシュ(//
)が含まれている可能性があり、パターン一致が失敗する可能性もあります。一部のコンテナーは、サーブレットマッピングを実行する前にこれらを正規化しますが、そうでないコンテナーもあります。このような課題から保護するために、FilterChainProxy
は HttpFirewall
戦略を使用して、リクエストをチェックしてラップします。デフォルトでは、正規化されていないリクエストは自動的に拒否され、パスパラメーターと重複するスラッシュは照合のために削除されます。(したがって、たとえば、/secure;hack=1/somefile.html;hack=2
の元のリクエストパスは /secure/somefile.html
として返されます)したがって、セキュリティフィルターチェーンを管理するために FilterChainProxy
を使用することが不可欠です。servletPath
と pathInfo
の値はコンテナーによってデコードされるため、これらの部分は照合のために削除されるため、アプリケーションにはセミコロンを含む有効なパスを含めないでください。
前述のように、デフォルトの戦略はマッチングに Ant スタイルのパスを使用することであり、これはほとんどのユーザーにとって最良の選択である可能性があります。この戦略はクラス AntPathRequestMatcher
に実装されており、Spring の AntPathMatcher
を使用して、queryString
を無視して、連結された servletPath
および pathInfo
に対して大文字と小文字を区別しないパターンの一致を実行します。
より強力なマッチング戦略が必要な場合は、正規表現を使用できます。戦略の実装は RegexRequestMatcher
です。詳細については、RegexRequestMatcher
(Javadoc) 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
}
クロスサイトトレース (XST) [OWASP] (英語) および HTTP 動詞の改ざん [OWASP] (英語) から保護するために、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 ʀ は、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 として解析することを検討できます。
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()
}