注意: この記事はLLMによって英語から翻訳されたものです。正確性については保証いたしかねますので、あらかじめご了承ください。英語の原文はこちら。
クロスサイトリクエストフォージェリ(CSRF)は、Webアプリケーションを標的にする攻撃手法である。典型的には、ユーザーがログインしている場所で意図せずアクションを実行させるものである。
本記事では、このような攻撃の実行方法と、それに対する防御方法について説明する。本記事はこのトピックの入門を目的としているため、すべての攻撃手法や対策を網羅するわけではない。より詳しく知りたい場合は、記事末尾の参考文献でこのテーマを深く掘り下げているものを参照されたい。
CSRF攻撃の実行方法#
簡単な例#
BobとAliceがともにフォーラムのメンバーだと想像しよう。何らかの理由でBobはAliceの発言が気に入らず、アカウントを削除させることで教訓を与えようと決めた。このプラットフォームはオープンソースソフトウェアを使用しており、コードを見ると、Bobはユーザー削除時に以下のGETリクエストが行われることがわかる:/users/delete?userName=[userName]&confirm=True
これを元に、Bobはフォーラムの管理者にメールを送り、何かの口実でforum.tld/delete?userName=Alice&confirm=TrueというURLをクリックさせる。管理者はそのリンクをクリックする際にフォーラムにログインしているため、Aliceのアカウント削除機能が呼び出され、アカウントは実際に削除される。
さて、アカウントは削除されるが、管理者は「アカウントは正常に削除されました」という趣旨のエラーメッセージを見ることになり、あまり目立たない方法とは言えない。管理者は何が起きたかに気づき、BobをBANするだろう。
では、Bobがそれほど簡単に検出されずに攻撃を実行する方法はあったのだろうか?あった。単にメールでリンクを共有する代わりに、0x0ピクセルの画像を含むHTML形式のメールを送ることができた:
<img src="http://forum.tld/delete?userName=Alice&confirm=True" width="0" height="0" border="0">
管理者がフォーラムにログインしているのと同じブラウザでHTMLメールを開いた場合、リクエストが実行され、何が起きたか気づくことができなかっただろう。
この例ではGETリクエストについて述べているが、POSTリクエストでもこの脆弱性を悪用することは可能である。そのためには、HTMLフォームを含むWebページを作成し、ページの読み込み時に自動送信されるようにすればよい。
要約#
まとめると、CSRF攻撃を成功させるためには3つの重要な要素が揃っている必要がある:
- HTTPリクエストが、被害者がターゲットWebサイトに認証されているブラウザから行われること
- 攻撃者がリクエストで期待されるパラメータとその値を知っていること。すべてのパラメータが予測可能であること
- ターゲットアプリケーションがセッションCookieに依存していること
CSRF攻撃からサービスを守る方法#
CSRF攻撃に対処する最も一般的な方法は、CSRFトークンを使用することである。これはサーバー側でリクエストごとに生成される、第三者にとって予測不可能な文字列であり、リクエストごとにサーバーによって検証される。
これらのトークンはシステムの状態を変更するすべてのリクエストに追加されるべきであり、GETパラメータとして渡されるべきではない(つまり、URLに含まれるべきではない)。その主な理由は、GETで渡されたものはさまざまな場所にログされたり、HTTPリクエストとともに送信されたりする可能性があるからである。したがって、状態を変更する操作にはGETリクエストを使用すべきではない。代わりに、例えばフォーム内の隠しフィールドを使ってトークンを渡すことが可能である:
<form action="//deluser" method="POST">
<input type="hidden" name="csrf-token" value="8927dd0eeb9b65500d148bdf7a144b598fc161c974d86e1ec18174ca7813ee8483f56035e28779aae83e11017b29fe08208b09d515cafb214e5defdbace07f36" />
<input type="text" name="username" />
<input type="submit" name="Delete the User" />
</form>サーバーレスのCSRF保護を管理する一つの方法は、リクエストを検証するために必要なすべての情報をトークン自体に含め、それを暗号化することである。例えば、sessionId timestampの形式で文字列を作成し、暗号化してトークンとして使用できる。
ユーザーがリクエストを行うと、サーバーはトークンを復号し、セッションIDが現在のユーザーに属していること、タイムスタンプが期待される有効期間内であることを確認するだけでよい。両方の要素が正しければ、サーバーは処理を続行できる。
タイムスタンプを使用する利点は、リプレイ攻撃を防止できることである。例えば、タイムスタンプが5分以上古くないことを要求するルールを設定できる。10分前のタイムスタンプを持つトークンを受け取った場合、リクエストは拒否される。
このセキュリティに加えて、ユーザーのパスワードを求めることや、アプリケーションがTOTPを実装している場合は重要な操作を検証するためにトークンを要求することも可能である。
その他の保護方法#
ダブルサブミットCookie#
CSRF攻撃からサービスを保護するための一般的な方法の一つに、ダブルサブミットCookieがある。リクエストごとにサーバーがトークン付きのCookieを生成し、そのトークンをリクエスト内にも配置する(例:フォームの隠しフィールド)。これは、別のドメインからCookieを書き込むことはできないという前提に基づいており、したがってCookieと隠しフィールドの値が同じであれば、リクエストは有効であるはずというものである。
これは正しいが、複数の欠陥がある。その一つは、ドメイン名のすべてのサブドメインを管理していない場合に発生する。サブドメインはメインドメインにCookieを書き込むことができ、どこで書き込まれたかを簡単に区別することができないためである。例えば、subdom1.dom.tldはdom.tldの下にCookieを書き込むことができる。そこから、攻撃者がsubdom1.dom.tldを管理しており、あなたのWebサイトがsubdom2.dom.tldにホストされている場合、subdom1を使ってCookieを書き込むことができ、あなたのアプリケーションはそれをsubdom2によって生成された正当なものとして読み取ってしまう。
なぜ単一のCookieだけでは不十分なのかというと、Cookieは出所に関係なくWebサイトにリクエストする際に常に送信されるからである。この問題を軽減する一つの方法(ただしすべての問題を解決するわけではない)は、SameSite Cookie属性を使用することで、特定のシナリオでのCookie送信を防止できる。ただし、SameSite Cookie属性はそれ単体では十分ではないため、他の対策と併用して実装すべきである。
Referヘッダーの使用#
CSRFを防止するもう一つの方法は、リクエストの検証時にオリジン/リファラーヘッダーを確認し、ヘッダーがリクエストが自サイトからのものであると示している場合にのみリクエストを実行することである。しかし、これだけでは十分とは考えられていない。プライバシーのためにこれらのヘッダーがnullに設定されたり、プロキシによって削除されたりする問題が発生する可能性があるからだ。
やや類似した方法として、JavaScriptを使用する場合、リクエスト時にカスタムヘッダーを設定する方法がある。ブラウザに実装されているSame Origin Policy(SOP)のおかげで、攻撃者がこのヘッダーを作成することは不可能であるため、CSRF防止に有効である。
出典#
- Bypassing CSRF Protections - A Double Defeat of the Double-Submit Cookie Pattern
- NetSparker: Using the Same-Site Cookie Attribute to Prevent CSRF Attacks
- NCC Group: Common CSRF Prevention Misconceptions
- OWASP: Cross Site Request Forgery
- OWASP: Cross-Site Request Forgery Prevention Cheat Sheet
- Play Framework: Protecting against Cross Site Request Forgery
- PortSwigger: What is CSRF?
- PortSwigger: CSRF Tokens
- PortSwigger: Defending against CSRF with SameSite cookies
- Stack Overflow: Why is the same origin policy so important?