最新の安定バージョンについては、Spring Security 6.3.1 を使用してください! |
クロスサイトリクエストフォージェリ (CSRF)
Spring は、クロスサイトリクエストフォージェリ (CSRF) [Wikipedia] 攻撃から保護するための包括的なサポートを提供します。以降のセクションでは、以下を検討します。
CSRF 攻撃とは何ですか?
CSRF 攻撃を理解する最良の方法は、具体例を見ることです。
銀行の Web サイトが、現在ログインしているユーザーから別の銀行口座に送金できるフォームを提供していると仮定します。例: 転送フォームは次のようになります。
<form method="post"
action="/transfer">
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="text"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
対応する HTTP リクエストは次のようになります。
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
次に、銀行の Web サイトに認証したふりをして、ログアウトせずに悪の Web サイトにアクセスします。邪悪な Web サイトには、次の形式の HTML ページが含まれています。
<form method="post"
action="https://bank.example.com/transfer">
<input type="hidden"
name="amount"
value="100.00"/>
<input type="hidden"
name="routingNumber"
value="evilsRoutingNumber"/>
<input type="hidden"
name="account"
value="evilsAccountNumber"/>
<input type="submit"
value="Win Money!"/>
</form>
お金を獲得したいため、送信ボタンをクリックします。このプロセスでは、意図せずに悪意のあるユーザーに 100 ドルを送金しました。これは、悪の Web サイトがあなたの Cookie を見ることができない一方で、銀行に関連付けられた Cookie がリクエストとともに送信されるためです。
最悪の場合、このプロセス全体が JavaScript を使用して自動化された可能性があります。つまり、ボタンをクリックする必要さえありませんでした。さらに、XSS 攻撃 [Wikipedia] の被害者である正直なサイトにアクセスすると、同じように簡単に発生する可能性があります。では、このような攻撃からユーザーをどのように保護するのでしょうか?
CSRF 攻撃からの保護
CSRF 攻撃が可能な理由は、被害者の Web サイトからの HTTP リクエストと攻撃者の Web サイトからのリクエストがまったく同じです。これは、悪の Web サイトからのリクエストを拒否し、銀行の Web サイトからのリクエストを許可する方法がないことを意味します。CSRF 攻撃から保護するために、2 つのリクエストを区別できるように、悪意のあるサイトが提供できないリクエストに何かがあることを確認する必要があります。
Spring は、CSRF 攻撃から保護するための 2 つのメカニズムを提供します。
セッション Cookie で SameSite 属性を指定する
両方の保護には安全なメソッドはべき等でなければなりませんが必要です |
安全なメソッドはべき等でなければなりません
CSRF に対するいずれかの保護が機能するためには、アプリケーションは「安全な」HTTP メソッドはべき等です [Mozilla] を確認する必要があります。これは、HTTP メソッド GET
、HEAD
、OPTIONS
、TRACE
を使用したリクエストがアプリケーションの状態を変更してはならないことを意味します。
シンクロナイザートークンパターン
CSRF 攻撃から保護するための支配的で最も包括的な方法は、シンクロナイザートークンパターン [OWASP] (英語) を使用することです。この解決策は、セッション Cookie に加えて、各 HTTP リクエストで、CSRF トークンと呼ばれる安全なランダム生成値が HTTP リクエストに存在する必要があることを確認することです。
HTTP リクエストが送信されると、サーバーは予想される CSRF トークンを検索し、HTTP リクエストの実際の CSRF トークンと比較する必要があります。値が一致しない場合、HTTP リクエストは拒否されます。
この動作の鍵は、実際の CSRF トークンが、ブラウザーによって自動的に含まれない HTTP リクエストの一部にある必要があることです。例: HTTP パラメーターまたは HTTP ヘッダーに実際の CSRF トークンをリクエストすると、CSRF 攻撃から保護されます。Cookie はブラウザーによって HTTP リクエストに自動的に含まれるため、Cookie で実際の CSRF トークンをリクエストすることは機能しません。
アプリケーションの状態を更新する各 HTTP リクエストに対して実際の CSRF トークンのみをリクエストするように期待を緩和できます。それが機能するためには、アプリケーションは安全な HTTP メソッドがべき等であることを保証する必要があります。これにより、外部サイトからのリンクを使用して Web サイトへのリンクを許可するため、使いやすさが向上します。さらに、ランダムトークンを HTTP GET に含めないようにします。これにより、トークンがリークする可能性があります。
Synchronizer Token Pattern を使用した場合の例がどのように変化するかを見てみましょう。実際の CSRF トークンは、_csrf
という名前の HTTP パラメーターに含まれている必要があると仮定します。アプリケーションの転送フォームは次のようになります。
<form method="post"
action="/transfer">
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="hidden"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
フォームには、CSRF トークンの値を持つ非表示の入力が含まれています。外部サイトは CSRF トークンを読み取ることができません。同じ発信元ポリシーにより、悪サイトはレスポンスを読み取れないためです。
送金に対応する HTTP リクエストは次のようになります。
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
HTTP リクエストに、安全なランダム値を持つ _csrf
パラメーターが含まれていることに気付くでしょう。悪の Web サイトは、_csrf
パラメーター(悪の Web サイトで明示的に提供する必要があります)に正しい値を提供できず、サーバーが実際の CSRF トークンと予想される CSRF トークンを比較すると、転送は失敗します。
SameSite 属性
CSRF 攻撃から保護する新しい方法は、Cookie で SameSite 属性 [Mozilla] を指定することです。サーバーは、Cookie を設定するときに SameSite
属性を指定して、外部サイトから来るときに Cookie を送信しないように指定できます。
Spring Security はセッション Cookie の作成を直接制御しないため、SameSite 属性のサポートは提供しません。Spring Session は、サーブレットベースのアプリケーションで |
たとえば、SameSite
属性を持つ HTTP レスポンスヘッダーは次のようになります。
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax
SameSite
属性の有効な値は次のとおりです。
Strict
- 指定すると、同じサイト [Mozilla] からのリクエストには Cookie が含まれます。それ以外の場合、Cookie は HTTP リクエストに含まれません。Lax
- 同じサイト [Mozilla] から来るとき、またはトップレベルのナビゲーションからリクエストが来て、メソッドがべき等であるときに、指定されたクッキーが送信されるとき。それ以外の場合、Cookie は HTTP リクエストに含まれません。
SameSite
属性を使用してこの例を保護する方法を見てみましょう。銀行アプリケーションは、セッション Cookie で SameSite
属性を指定することにより、CSRF から保護できます。
セッション Cookie に SameSite
属性が設定されていると、ブラウザーは、銀行の Web サイトからのリクエストとともに JSESSIONID
Cookie を送信し続けます。ただし、悪意のある Web サイトからの転送リクエストを含む JSESSIONID
Cookie はブラウザーから送信されなくなります。悪の Web サイトからの転送リクエストにはセッションが存在しないため、アプリケーションは CSRF 攻撃から保護されます。
SameSite
属性を使用して CSRF 攻撃から保護する場合に注意すべき重要な考慮事項 [Mozilla] がいくつかあります。
SameSite
属性を Strict
に設定すると、強力な防御が提供されますが、ユーザーを混乱させる可能性があります。social.example.com (英語) でホストされているソーシャルメディアサイトにログインしたままのユーザーを考えます。ユーザーは、email.example.org (英語) でソーシャルメディアサイトへのリンクを含むメールを受信します。ユーザーがリンクをクリックすると、ソーシャルメディアサイトへの認証が期待されます。ただし、SameSite
属性が Strict
の場合、Cookie は送信されないため、ユーザーは認証されません。
gh-7537 [GitHub] (英語) を実装することにより、CSRF 攻撃に対する |
もう 1 つの明らかな考慮事項は、SameSite
属性がユーザーを保護するために、ブラウザーが SameSite
属性をサポートする必要があるということです。最新のブラウザーのほとんどは、SameSite 属性をサポートしています [Mozilla] 。ただし、まだ使用されている古いブラウザーはそうではない場合があります。
このため、CSRF 攻撃に対する唯一の保護ではなく、SameSite
属性を徹底的な防御として使用することをお勧めします。
CSRF 保護を使用する場合
いつ CSRF 保護を使用する必要がありますか? 通常のユーザーがブラウザーで処理できるリクエストには CSRF 保護を使用することをお勧めします。ブラウザー以外のクライアントが使用するサービスのみを作成する場合は、CSRF 保護を無効にすることをお勧めします。
CSRF 保護と JSON
よくある質問に、「javascript で作られた JSON リクエストを保護する必要があるのか?」というものがあります。簡単に言うと場合によります。しかし、JSON リクエストに影響を与える CSRF エクスプロイトが存在するため、十分に注意す ' る必要があります。例: 悪意のあるユーザーが次の形式を使用した JSON を使用した CSRF (英語) を作成できます:
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
これにより、次の JSON 構造が生成されます
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}
アプリケーションが Content-Type を検証していない場合、このエクスプロイトにさらされる可能性があります。設定によっては、Content-Type を検証する Spring MVC アプリケーションは、次に示すように URL サフィックスを .json
で終わるように更新することにより、依然として悪用される可能性があります。
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
CSRF およびステートレスブラウザーアプリケーション
アプリケーションがステートレスの場合はどうなるでしょうか? それは必ずしも保護されているという意味ではありません。実際、ユーザーが特定のリクエストに対して Web ブラウザーでアクションを実行する必要がない場合、ユーザーは CSRF 攻撃に対して依然として脆弱です。
例: JSESSIONID の代わりに、認証のためにすべての状態を含むカスタム Cookie を使用するアプリケーションを検討します。CSRF 攻撃が行われると、前の例で JSESSIONID Cookie が送信されたのと同じ方法で、リクエストとともにカスタム Cookie が送信されます。このアプリケーションは、CSRF 攻撃に対して脆弱です。
基本認証を使用するアプリケーションも CSRF 攻撃に対して脆弱です。前の例で JSESSIONID Cookie が送信されたのと同じ方法で、ブラウザーがリクエストにユーザー名とパスワードを自動的に含めるため、アプリケーションは脆弱です。
CSRF の考慮事項
CSRF 攻撃に対する保護を実装する際に考慮すべき特別な考慮事項がいくつかあります。
ログイン
ログインリクエストの偽造を防ぐに [Wikipedia] は、ログイン HTTP リクエストを CSRF 攻撃から保護する必要があります。悪意のあるユーザーが被害者の機密情報を読み取れないように、ログインリクエストを偽造から保護する必要があります。攻撃は次のように実行されます。
悪意のあるユーザーは、悪意のあるユーザーの資格情報を使用して CSRF ログインを実行します。これで、被害者は悪意のあるユーザーとして認証されます。
悪意のあるユーザーは、標的の Web サイトにアクセスして機密情報を入力するように被害者をだます
情報は悪意のあるユーザーのアカウントに関連付けられているため、悪意のあるユーザーは自分の資格情報でログインし、被害者の機密情報を表示できます
ログイン HTTP リクエストが CSRF 攻撃から保護されるようにするための複雑な問題は、リクエストが拒否される原因となるセッションタイムアウトがユーザーに発生する可能性があることです。セッションタイムアウトは、ログインするためにセッションを必要としないユーザーにとっては驚きです。詳細については、CSRF およびセッションタイムアウトを参照してください。
ログアウト
ログアウトリクエストの偽造を防ぐには、ログアウト HTTP リクエストを CSRF 攻撃から保護する必要があります。悪意のあるユーザーが被害者の機密情報を読み取れないように、ログアウトリクエストの偽造に対する保護が必要です。攻撃の詳細については、このブログ投稿を参照して (英語) ください。
ログアウト HTTP リクエストが CSRF 攻撃から保護されることを確実にするための複雑な問題は、リクエストが拒否される原因となるセッションタイムアウトがユーザーに発生する可能性があることです。セッションタイムアウトは、ログアウトするためにセッションを持つ必要がないと考えているユーザーにとっては驚くべきことです。詳細については、CSRF およびセッションタイムアウトを参照してください。
CSRF およびセッションタイムアウト
多くの場合、予想される CSRF トークンはセッションに保存されます。これは、セッションが期限切れになるとすぐに、サーバーが期待される CSRF トークンを見つけられず、HTTP リクエストを拒否することを意味します。タイムアウトを解決するための多くのオプションがあり、それぞれにトレードオフがあります。
タイムアウトを軽減する最善の方法は、JavaScript を使用してフォーム送信時に CSRF トークンをリクエストすることです。その後、フォームは CSRF トークンで更新され、送信されます。
別のオプションは、セッションが期限切れになることをユーザーに知らせる JavaScript を用意することです。ユーザーはボタンをクリックしてセッションを続行し、リフレッシュできます。
最後に、期待される CSRF トークンを Cookie に保存できます。これにより、予想される CSRF トークンがセッションより長く存続できます。
予想される CSRF トークンがデフォルトで Cookie に保存されない理由を確認する人がいるかもしれません。これは、ヘッダー(Cookie の指定など)が別のドメインによって設定される可能性がある既知のエクスプロイトが存在するためです。これは、ヘッダー X-Requested-With が存在する場合 (英語) 、 Rails 上の Ruby が CSRF チェックをスキップしない (英語) のと同じ理由です。エクスプロイトの実行メソッドの詳細については、この webappsec.org スレッド (英語) を参照してください。もう 1 つの欠点は、状態(つまり、タイムアウト)を削除することで、トークンが危険にさらされた場合にトークンを強制的に無効にする機能を失うことです。
マルチパート (ファイルアップロード)
CSRF 攻撃からマルチパートリクエスト(ファイルのアップロード)を保護すると、鶏と卵 [Wikipedia] の問題が発生します。CSRF 攻撃の発生を防ぐには、HTTP リクエストの本文を読み取って実際の CSRF トークンを取得する必要があります。ただし、本文を読むことは、ファイルがアップロードされることを意味し、外部サイトがファイルをアップロードできることを意味します。
multipart/form-data で CSRF 保護を使用するには、2 つのオプションがあります。各オプションにはトレードオフがあります。
Spring Security の CSRF 保護をマルチパートファイルアップロードと統合する前に、CSRF 保護なしでアップロードできることを確認してください。Spring でのマルチパートフォームの使用に関する詳細情報は、Spring リファレンスの 1.1.11. マルチパートリゾルバーセクションおよび MultipartFilter javadoc 内にあります。 |
CSRF トークンを本文に配置する
最初のオプションは、リクエストの本文に実際の CSRF トークンを含めることです。CSRF トークンを本文に配置することにより、認証が実行される前に本文が読み取られます。これは、誰でもサーバーに一時ファイルを配置できることを意味します。ただし、認可されたユーザーのみが、アプリケーションによって処理されるファイルを送信できます。一般に、一時ファイルのアップロードによるほとんどのサーバーへの影響はごくわずかであるため、これが推奨されるアプローチです。
URL に CSRF トークンを含める
認可されていないユーザーが一時ファイルをアップロードすることを認可しない場合は、フォームのアクション属性にクエリパラメーターとして予想される CSRF トークンを含めることもできます。このアプローチの欠点は、クエリパラメーターがリークする可能性があることです。より一般的には、機密データが漏れないように、本文またはヘッダー内に機密データを配置することをお勧めします。追加情報は URI の機密情報をエンコードする RFC 2616 セクション 15.1.3 [W3C] (英語) にあります。
HiddenHttpMethodFilter
一部のアプリケーションでは、HTTP メソッドをオーバーライドするためにフォームパラメーターを使用できます。例: 以下の形式は、HTTP メソッドを post
ではなく delete
として扱うために使用できます。
<form action="/process"
method="post">
<!-- ... -->
<input type="hidden"
name="_method"
value="delete"/>
</form>
HTTP メソッドのオーバーライドはフィルターで発生します。そのフィルターは、Spring Security のサポートの前に配置する必要があります。オーバーライドは post
でのみ発生するため、実際に問題が発生する可能性はほとんどありません。ただし、Spring Security のフィルターの前に配置することをお勧めします。