CSRF防止トークンをCookieに入れるのはなぜ一般的ですか?


283

CSRFの問題全体とそれを防ぐための適切な方法を理解しようとしています。(私が読み、理解し、同意したリソース:OWASP CSRF防止に関するチートシートCSRFに関する質問

私が理解しているように、CSRFに関連する脆弱性は、(Webサーバーの観点から)着信HTTPリクエストの有効なセッションCookieが認証済みユーザーの希望を反映しているという仮定によって導入されています。ただし、オリジンドメインのすべてのCookieはブラウザーによってリクエストに魔法のようにアタッチされているため、実際のサーバーは、リクエスト内の有効なセッションCookieの存在から、認証されたセッションを持つブラウザーからのリクエストであると推測できます。それはコードについて何も仮定することはできませんそのブラウザで実行している、またはそれが本当にユーザーの希望を反映しているかどうか。これを防ぐ方法は、リクエストに追加の認証情報(「CSRFトークン」)を含めることです。これは、ブラウザの自動Cookie処理以外の方法で行われます。大まかに言えば、セッションCookieはユーザー/ブラウザーを認証し、CSRFトークンはブラウザーで実行されているコードを認証します。

つまり、簡単に言うと、セッションCookieを使用してWebアプリケーションのユーザーを認証している場合は、各応答にCSRFトークンを追加し、各(変更)リクエストで一致するCSRFトークンを要求する必要があります。次に、CSRFトークンはサーバーからブラウザーへとサーバーからサーバーへの往復を行い、要求を行っているページがそのサーバーによって承認されている(生成されている)ことをサーバーに証明します。

私の質問に戻ります。これは、その往復でそのCSRFトークンに使用される特定の転送方法についてです。

AngularJSDjangoRailsなどで)CSRFトークンをサーバーからクライアントにCookieとして(つまり、Set-Cookieヘッダーで)送信し、クライアントのJavascriptでCookieから削ってそれを添付するのが一般的ですサーバーに送り返す別のXSRF-TOKENヘッダーとして。

(別の方法は、たとえばExpressによって推奨される方法です。サーバーによって生成されたCSRFトークンは、サーバー側のテンプレート拡張を介して応答本文に含まれ、サーバーに戻すコード/マークアップに直接添付されます。非表示のフォーム入力として。この例は、よりWeb 1.0風の方法ですが、より一般的なJSの重いクライアントに一般化されます。

CSRFトークンのダウンストリームトランスポートとしてSet-Cookieを使用するのはなぜそれほど一般的ですか。なぜこれが良いアイデアなのでしょうか。これらすべてのフレームワークの作成者がオプションを慎重に検討し、これを間違えなかったと思います。しかし、一見したところ、基本的にCookieの設計上の制限を回避するためにCookieを使用することは、お粗末なようです。実際、ラウンドトリップトランスポートとしてCookie(サーバーの下流にSet-Cookie:ヘッダーがブラウザにCSRFトークンを通知し、ブラウザにそれをサーバーに返すようにCookie:ヘッダーを上流に送信する)を使用した場合、脆弱性が再導入されます。修正しようとしています。

上記のフレームワークは、CSRFトークンのラウンドトリップ全体でCookieを使用しないことに気づきました。彼らはSet-Cookieダウンストリームを使用し、次に他の何か(X-CSRF-Tokenヘッダーなど)をアップストリームに使用します。これにより、脆弱性が排除されます。ただし、ダウンストリームトランスポートとしてSet-Cookieを使用しても、誤解を招く可能性があり、危険です。これで、ブラウザはCSRFトークンをすべての要求に添付し、本物の悪意のあるXSRF要求を含みます。せいぜい要求が必要以上に大きくなり、最悪の場合でも意味のあるサーバーコードの一部が実際にそれを使用しようとする可能性があり、これは本当に悪いことです。さらに、CSRFトークンの実際の受信者はクライアント側のJavascriptであるため、このCookieをhttpのみで保護することはできません。


それは適切な場所に当てはまる素晴らしい質問です。
kta

回答:


262

あなたが少し触れた良い理由は、CSRF Coo​​kieが受信されると、アプリケーション全体でクライアントスクリプトで使用できるようになり、通常のフォームとAJAX POSTの両方で使用できるようになるためです。これは、AngularJSで採用されているようなJavaScriptの重いアプリケーションで理にかなっています(AngularJSを使用する場合、アプリケーションが単一ページアプリである必要はないため、CSRF値が異なるページリクエスト間で状態をフローする必要がある場合に役立ちます。通常はブラウザに保持できません)。

説明する各アプローチの長所と短所について、一般的なアプリケーションで次のシナリオとプロセスを検討してください。これらは、シンクロナイザトークンパターンに基づいています。

ボディアプローチのリクエスト

  1. ユーザーは正常にログインしました。
  2. サーバーは認証Cookieを発行します。
  3. ユーザーがクリックしてフォームに移動します。
  4. このセッションに対してまだ生成されていない場合、サーバーはCSRFトークンを生成し、それをユーザーセッションに対して保存して、非表示フィールドに出力します。
  5. ユーザーがフォームを送信します。
  6. サーバーは、隠しフィールドがセッションに保存されたトークンと一致するかどうかをチェックします。

利点:

  • 実装が簡単です。
  • AJAXで動作します。
  • フォームで動作します。
  • Cookieは実際にはHTTPのみにすることができます

短所:

  • すべてのフォームは、隠しフィールドをHTMLで出力する必要があります。
  • AJAX POSTにも値を含める必要があります。
  • ページは、CSRFトークンが必要であることを事前に知っている必要があります。これにより、ページコンテンツにトークンを含めることができるため、すべてのページにどこかにトークン値を含める必要があり、大規模なサイトの実装に時間がかかる可能性があります。

カスタムHTTPヘッダー(ダウンストリーム)

  1. ユーザーは正常にログインしました。
  2. サーバーは認証Cookieを発行します。
  3. ユーザーがクリックしてフォームに移動します。
  4. ブラウザでページが読み込まれ、CSRFトークンを取得するためにAJAX要求が行われます。
  5. サーバーはCSRFトークンを生成し(セッション用にまだ生成されていない場合)、ユーザーセッションに対して保存し、ヘッダーに出力します。
  6. ユーザーがフォームを送信します(トークンは非表示フィールドを介して送信されます)。
  7. サーバーは、隠しフィールドがセッションに保存されたトークンと一致するかどうかをチェックします。

利点:

短所:

  • ヘッダー値を取得するAJAXリクエストがないと機能しません。
  • すべてのフォームには、HTMLに動的に値を追加する必要があります。
  • AJAX POSTにも値を含める必要があります。
  • CSRFトークンを取得するには、ページが最初にAJAX要求を行う必要があるため、毎回余分なラウンドトリップが発生します。
  • 同様に、トークンをページに出力するだけで、余分なリクエストが保存される可能性があります。

カスタムHTTPヘッダー(アップストリーム)

  1. ユーザーは正常にログインしました。
  2. サーバーは認証Cookieを発行します。
  3. ユーザーがクリックしてフォームに移動します。
  4. このセッションに対してまだ生成されていない場合、サーバーはCSRFトークンを生成し、それをユーザーセッションに対して格納し、ページコンテンツのどこかに出力します。
  5. ユーザーはAJAX経由でフォームを送信します(トークンはヘッダー経由で送信されます)。
  6. サーバーは、カスタムヘッダーがセッションに格納されたトークンと一致するかどうかを確認します。

利点:

短所:

  • フォームでは機能しません。
  • すべてのAJAX POSTにはヘッダーを含める必要があります。

カスタムHTTPヘッダー(アップストリームとダウンストリーム)

  1. ユーザーは正常にログインしました。
  2. サーバーは認証Cookieを発行します。
  3. ユーザーがクリックしてフォームに移動します。
  4. ブラウザでページが読み込まれ、CSRFトークンを取得するためにAJAX要求が行われます。
  5. サーバーはCSRFトークンを生成し(セッション用にまだ生成されていない場合)、ユーザーセッションに対して保存し、ヘッダーに出力します。
  6. ユーザーはAJAX経由でフォームを送信します(トークンはヘッダー経由で送信されます)。
  7. サーバーは、カスタムヘッダーがセッションに格納されたトークンと一致するかどうかを確認します。

利点:

短所:

  • フォームでは機能しません。
  • すべてのAJAX POSTにも値を含める必要があります。
  • CRSFトークンを取得するには、ページが最初にAJAX要求を行う必要があるため、毎回余分なラウンドトリップが発生します。

セットクッキー

  1. ユーザーは正常にログインしました。
  2. サーバーは認証Cookieを発行します。
  3. ユーザーがクリックしてフォームに移動します。
  4. サーバーはCSRFトークンを生成し、ユーザーセッションに対して保存し、Cookieに出力します。
  5. ユーザーは、AJAXまたはHTMLフォームを介してフォームを送信します。
  6. サーバーは、カスタムヘッダー(または非表示のフォームフィールド)がセッションに格納されたトークンと一致するかどうかを確認します。
  7. ブラウザでCookieを使用して、CSRFトークンを取得するためのサーバーへの追加リクエストなしで、追加のAJAXおよびフォームリクエストで使用できます。

利点:

  • 実装が簡単です。
  • AJAXで動作します。
  • フォームで動作します。
  • Cookie値を取得するためにAJAXリクエストを必ずしも必要としない。HTTPリクエストはそれを取得でき、JavaScriptを介してすべてのフォーム/ AJAXリクエストに追加できます。
  • CSRFトークンが取得されると、Cookieに格納されるため、追加のリクエストなしで値を再利用できます。

短所:

  • すべてのフォームには、HTMLに動的に値を追加する必要があります。
  • AJAX POSTにも値を含める必要があります。
  • Cookieはリクエストサイズ(CSRFプロセスに関係しない画像、CSS、JSなどのすべてのGET)を送信するたびに送信されます。
  • CookieをHTTPのみにすることはできません。

したがって、Cookieのアプローチはかなり動的であり、Cookieの値(任意のHTTPリクエスト)を取得して使用する簡単な方法を提供します(JSは値を任意のフォームに自動的に追加でき、AJAXリクエストでヘッダーまたはヘッダーとして使用できますフォーム値)。CSRFトークンをセッションで受信すると、CSRFエクスプロイトを使用する攻撃者がこのトークンを取得する方法がないため、CSRFトークンを再生成する必要はありません。悪意のあるユーザーが上記の方法のいずれかでユーザーのCSRFトークンを読み取ろうとした場合、これはSame Origin Policyによって防止されます。悪意のあるユーザーがCSRFトークンサーバー側を取得しようとした場合(例:curl)その後、このトークンは、被害者の認証セッションCookieがリクエストから欠落しているため、同じユーザーアカウントに関連付けられません(攻撃者のものになるため、被害者のセッションとサーバー側は関連付けられません)。

だけでなく、シンクロナイザートークンのパターンも存在するダブル提出クッキーもちろんCookieを使用してCSRFトークンのタイプを保存するCSRF防止方法。これは、CSRFトークンにサーバー側の状態を必要としないため、実装が簡単です。この方法を使用する場合、CSRFトークンは実際には標準の認証Cookieである可能性があり、この値はリクエストとともに通常どおりCookie経由で送信されますが、値は非表示フィールドまたはヘッダーでも繰り返されます。そもそも値を読み取れません。ただし、認証Cookie以外の別のCookieを選択することをお勧めします。これにより、認証CookieはHttpOnlyとマークされることで保護されます。したがって、これは、Cookieベースの方法を使用したCSRF防止を見つけるもう1つの一般的な理由です。


7
「CSRFトークンを取得するためにAJAXリクエストが行われる」(両方の「カスタムヘッダー:ダウンストリーム」セクションのステップ4)が安全に実行できる方法が理解できません。これは別個の要求であるため、サーバーはそれが誰から送信されたかを知りません。CSRFトークンを漏らすことが安全であることをどのようにして知るのですか?トークンを最初のページのロードから取得できない場合は、失うことになります(残念ながら、カスタムダウンストリームレスポンスヘッダーはスターターではありません)。
metamatt 2013

6
偽造者はセッションCookieを持たないからです。彼らは独自のセッションCookieを持っている可能性がありますが、CSRFトークンがセッションに関連付けられているため、CSRFトークンは被害者のものと一致しません。
SilverlightFox

32
CSRF攻撃についての私の理解では、偽造者は私のセッションCookieを持っいます。まあ、実際にはCookie を表示することはできませんが、偽造されたリクエストでそれを提供する機能があります。これは、リクエストがブラウザーからのものであり、ブラウザーがセッションCookieを提供するためです。したがって、サーバーの観点からは、セッションCookieだけでは正当な要求と偽造された要求を区別できません。これは実際に私たちが防ごうとしている攻撃です。ところで、私がこれについて混乱している場合は特に、これを話すのにあなたの忍耐力をありがとう。
メタマット2013

8
認証Cookieを提供することはできますが、CSRFトークンを含む応答を読み取ることはできません。
SilverlightFox

8
@metamattネクロは申し訳ありませんが、さまよっている人のためにやります。私の理解では、攻撃者は通常、応答にアクセスできません。CSRFは、直接データを収集するのではなく、主に副作用引き起こすために使用されます。たとえば、CSRF攻撃スクリプトは、特権ユーザーに攻撃者の特権をエスカレートさせたり、セキュリティ設定を無効にしたり、ログインしているペイパルユーザーに特定の電子メールアドレスに転送を送信させたりする可能性があります。これらのいずれの場合でも、攻撃者は応答を気にしません。応答は犠牲者のブラウザーに送信されます。攻撃の結果のみ。
jonathanbruder

61

Cookieを使用してクライアントにCSRFトークンを提供することはできません。攻撃者はCookieの値を読み取ることができないため、サーバー側のCSRF検証で必要な場所に配置することができないためです。

攻撃者は、リクエストヘッダーに認証トークンCookieとCSRF Coo​​kieの両方を使用して、サーバーにリクエストを送信することができます。ただし、サーバーはCSRFトークンをリクエストヘッダーのCookieとして探しているのではなく、リクエストのペイロードを探しています。また、攻撃者がCSRFトークンをペイロードのどこに配置するかを知っていても、その値を読み取ってそこに配置する必要があります。ただし、ブラウザのクロスオリジンポリシーにより、ターゲットWebサイトからCookie値を読み取ることができなくなります。

サーバーがリクエストヘッダーでそれを期待し、攻撃者がそこに置くために特別なことをする必要がないため、同じロジックはauthトークンcookieには適用されません。


確かに、攻撃者は最初からCookieを読み取る必要はありません。彼らはsrc='bank.com/transfer?to=hacker&amount=1000、ブラウザが要求するハッキングされたサイトに画像を挿入するだけで、そのサイトに関連付けられたCookieを使用できます(bank.com)?
開発者

2
CSRFはクライアント側でユーザーを検証するためのものであり、一般的にはサーバー側の侵害からサイトを保護するためのものではありません。
Tongfa 2017年

2
@developiusがCookieを送信しても、CSRF保護を満たすには不十分です。Cookieには、サーバーから送信されたcsrfトークンが含まれています。正当なクライアントはcookieからcsrfトークンを読み取り、それをヘッダーやペイロードなどのどこかにリクエストで渡す必要があります。CSRF保護は、Cookieの値が要求の値と一致することを確認します。一致しない場合、要求は拒否されます。したがって、攻撃者はCookieを読み取る必要があります。
ウィルM.

1
この回答は、元の投稿者の質問を非常に的確にとらえ、非常に明確でした。+1ありがとうございます。
java-addict301

@Tongfa-ありがとう、これは私がよりよく理解するのに役立ちました。CSRFトークンをヘッダーに配置しないでください。それは体のどこかにあるに違いない?
-zerohedge

10

答えについては私の推測が最もよく当てはまります。サーバーからブラウザにCSRFトークンを取得する方法として、次の3つのオプションを検討してください。

  1. リクエスト本文(HTTPヘッダーではありません)。
  2. Set-Cookieではなく、カスタムHTTPヘッダー。
  3. Cookieとして、Set-Cookieヘッダー内。

最初のリクエストボディ(質問でリンクしたExpressチュートリアルで説明されていますが)は、さまざまな状況に移植可能ではないと思います。すべての人がすべてのHTTP応答を動的に生成しているわけではありません。生成された応答にトークンを配置する必要が生じる場所は、さまざまです(非表示のフォーム入力、JSコードのフラグメント、または他のJSコードからアクセス可能な変数で、URLでさえ、一般的には悪い場所のようですが) CSRFトークンを配置します)。したがって、いくつかのカスタマイズで機能する一方で、#1は万能のアプローチを実行するのが難しい場所です。

2番目のカスタムヘッダーは魅力的ですが、JSは呼び出したXHRのヘッダーを取得できますが、ロード元のページのヘッダーを取得できないため、実際には機能しません。

3つ目は、すべての状況で使いやすいアプローチとして、Set-Cookieヘッダーによって運ばれるCookieです(誰のサーバーでも要求ごとのCookieヘッダーを設定でき、どのような種類でも問題ありません)データはリクエスト本文にあります)。したがって、その欠点にも関わらず、フレームワークを広く実装するのが最も簡単な方法でした。


7
私は明白に述べているかもしれませんが、これはcookieがhttponlyで正しくないことを意味しますか?
フォトン

1
ajaxリクエストの場合のみ(JSは、2番目のチャネルの次のリクエストで(フォームデータまたはヘッダーとして)再送信するためにcsrf Cookieの値を知る必要があります)。セッションCookieがすでにHttpOnlyである場合(XSSから保護するため)、csrfトークンはHttpOnlyである必要がある理由はありません。csrfトークンは、関連付けられたセッションなしではそれ自体価値がないためです。
カウバート

2

セッションCookie(これは標準の一種です)の他に、追加のCookieを使用したくありません。

多くのAJAXリクエストを使用して、シングルページWebアプリケーション(SPA)を構築する際に役立つソリューションを見つけました。注:私はサーバー側のJavaとクライアント側のJQueryを使用していますが、魔法のようなものはないので、この原則はすべての一般的なプログラミング言語で実装できると思います。

追加のCookieを使用しない私のソリューションは簡単です:

クライアント側

ログイン成功後にサーバーから返されるCSRFトークンをグローバル変数に保存します(もちろん、グローバルストレージではなくWebストレージを使用する場合)。各AJAX呼び出しでX-CSRF-TOKENヘッダーを提供するようにJQueryに指示します。

メインの「インデックス」ページには、次のJavaScriptスニペットが含まれています。

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
    jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
}); 

サーバ側

ログインに成功したら、ランダムな(そして十分に長い)CSRFトークンを作成し、これをサーバー側セッションに格納して、クライアントに返します。X-CSRF-TOKENヘッダー値をセッションに保存されている値と比較して、特定の(機密)着信要求をフィルタリングします。これらは一致する必要があります。

機密性の高いAJAX呼び出し(POSTフォームデータとGET JSONデータ)、およびそれらをキャッチするサーバー側フィルターは、/ dataservice / *パスの下にあります。ログイン要求はフィルターにヒットしてはならないため、これらは別のパスにあります。HTML、CSS、JSおよび画像リソースのリクエストも/ dataservice / *パス上にないため、フィルタリングされません。これらには秘密は何も含まれておらず、害を及ぼすことはありません。これで問題ありません。

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
    resp.sendError(401);
} else
    chain.doFilter(request, response);
}   

ログインリクエストでCSRFが必要になると思います。CSRFトークンをログインセッショントークンとしても使用しているようです。また、それらを別個のトークンとして持つように機能し、ユーザーがログインしているかどうかに関係なく、任意のエンドポイントでCSRFを使用できます。
Tongfa
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.