私はCSRFの問題全体とそれを防ぐための適切な方法を理解しようとしています。 (私が読んだり、理解したり、同意したりしたリソース: OWASP CSRF防止チートシート 、 CSRFに関する質問 。)
私が理解しているように、CSRFのまわりの脆弱性は(Webサーバの観点から)入ってくるHTTPリクエストの中の有効なセッションクッキーが認証されたユーザの希望を反映するという仮定によってもたらされます。しかし、Originドメインのすべてのクッキーはブラウザによるリクエストに魔法のように関連付けられているため、リクエスト内の有効なセッションクッキーの存在からサーバが推測できるのは、認証されたセッションを持つブラウザからのリクエストです。そのブラウザで実行されているコードについて、またはそれが実際にユーザーの希望を反映しているかどうかについては、これ以上想定することはできません。これを防ぐ方法は、ブラウザの自動Cookie処理以外の方法で行われる追加の認証情報( "CSRFトークン")をリクエストに含めることです。大雑把に言うと、セッションCookieはユーザー/ブラウザを認証し、CSRFトークンはブラウザで実行されているコードを認証します。
つまり、一言で言えば、Webアプリケーションのユーザーを認証するためにセッションCookieを使用している場合は、各応答にCSRFトークンを追加し、それぞれの(変化する)要求に一致するCSRFトークンを要求する必要があります。その後、CSRFトークンはサーバーからブラウザーへ、サーバーからサーバーへの往復を行い、要求を出しているページがそのサーバーによって承認されている(さらにはそのサーバーによって生成されている)ことをサーバーに証明します。
私の質問ですが、これは、そのラウンドトリップでそのCSRFトークンに使用されている特定の転送方法についてです。
CSRFトークンをクッキーとしてサーバーからクライアントに送信するのは一般的なようです(例えば AngularJS 、 Django 、 Rails )。そして、クライアントのJavascriptにクッキーからそれを削り取らせ、それを別のXSRF-TOKENヘッダーとして添付してサーバーに返送させます。
(代替の方法は、例えば Express で推奨されているものです。サーバーによって生成されたCSRFトークンは、サーバーサイドのテンプレート拡張を介してレスポンスボディに含まれ、提供するコード/マークアップに直接添付されます。その例は、隠しフォームの入力としてサーバに返されるものです。
CSRFトークンのダウンストリームトランスポートとしてSet-Cookieを使用することがそれほど一般的なのはなぜですか。私は、これらすべてのフレームワークの作者がそれらのオプションを慎重に検討し、これを誤解していなかったと思います。しかし、一見したところ、基本的にクッキーの設計上の制限であるものを回避するためにクッキーを使用するのは当たり前のようです。実際、ラウンドトリップトランスポートとしてCookieを使用した場合(ブラウザにCSRFトークンを通知するためにSet-Cookie:サーバーの下流に、ブラウザにCookie:ヘッダーを送信してサーバーに返す)には、次の脆弱性を再導入します修正しようとしています。
私は、上記のフレームワークがCSRFトークンのためのラウンドトリップ全体のためにクッキーを使わないことを私は理解しています。彼らはSet-Cookieを下流に使用し、それから何か他のもの(例えばX-CSRF-Tokenヘッダ)を上流に使用し、これは脆弱性を排除します。しかし、下流のトランスポートとしてSet-Cookieを使用したとしても、誤解を招く可能性があり危険です。ブラウザは、本物の悪意のあるXSRFリクエストを含むすべてのリクエストにCSRFトークンを添付します。せいぜい、要求を必要以上に大きくして、最悪の場合、ある意味ではあるが見当違いのサーバーコードが実際にそれを使用しようとするかもしれませんが、それは本当に悪いことです。さらに、CSRFトークンの実際の受信者はクライアントサイドのJavascriptなので、このCookieはhttp-onlyで保護することはできません。したがって、Set-CookieヘッダーでCSRFトークンをダウンストリームに送信することは、私にとっては最適とは言えません。
あなたが手を触れてきた正当な理由は、一度CSRFクッキーが受け取られたら、それが通常のフォームとAJAXPOSTの両方で使用するためにクライアントスクリプトでアプリケーションを通して使用可能であるということです。これは、AngularJSで使用されるようなJavaScriptを多用するアプリケーションでは意味があります(AngularJSを使用すると、アプリケーションが単一ページのアプリケーションになる必要はないので、CSRF値が異なるページ要求間で状態が流れる必要がある場合に役立ちます)。通常はブラウザに保存できません。
ここで説明する各アプローチの長所と短所については、典型的なアプリケーションにおける以下のシナリオとプロセスを検討してください。これらは Synchronizer Token Pattern に基づいています。
利点:
デメリット:
利点:
デメリット:
利点:
デメリット:
利点:
デメリット:
利点:
デメリット:
そのため、cookieのアプローチはかなり動的であり、cookieの値(任意のHTTP要求)を取得して使用する簡単な方法を提供します(JSは任意のフォームに値を自動的に追加し、AJAX要求で使用できます)。ヘッダーまたはフォーム値として) CSRFトークンがセッションに対して受信されると、CSRFエクスプロイトを使用する攻撃者はこのトークンを取得する方法がないため、それを再生成する必要はありません。悪意のあるユーザーが上記の方法のいずれかでユーザーのCSRFトークンを読み取ろうとすると、これは Same Origin Policy によって防止されます。悪意のあるユーザーがCSRFトークンサーバー側を(たとえばcurl
を介して)取得しようとすると、このトークンは被害者の認証セッションCookieがリクエストから失われるため、同じユーザーアカウントに関連付けられません。サーバー側が被害者のセッションに関連付けられることはありません。
Synchronizer Token Pattern 同様に Double Submit Cookie CSRF防止方法もあります。もちろん、これはクッキーを使用してCSRFトークンのタイプを格納します。 CSRFトークンにサーバー側の状態を必要としないため、これは実装が簡単です。このメソッドを使用する場合、実際にはCSRFトークンが標準の認証Cookieになる可能性があります。この値は通常のリクエストと同様にCookie経由で送信されますが、値は隠しフィールドまたはヘッダーにも繰り返されます。そもそも値を読むことはできません。ただし、HttpOnlyとマークされて認証Cookieが保護されるように、認証Cookie以外の別のCookieを選択することをお勧めします。ですから、これが、Cookieベースの方法を使用したCSRF防止を見つけるもう1つの一般的な理由です。
攻撃者はCookieの値を読み取ることができず、サーバー側のCSRF検証で要求される場所にCookieを配置できないため、Cookieを使用してクライアントにCSRFトークンを提供しても攻撃を成功させることはできません。
攻撃者は、リクエストヘッダーに認証トークンCookieとCSRF Cookieの両方を使用してサーバーにリクエストを送信することができます。しかし、サーバーはCSRFトークンを要求ヘッダー内のCookieとして探しているのではなく、要求のペイロードを探しています。そして攻撃者がペイロードのどこにCSRFトークンを入れるべきかを知っていても、それをそこに置くために彼らはその値を読まなければならないでしょう。しかし、ブラウザのクロスオリジンポリシーにより、ターゲットWebサイトからCookieの値を読み取ることはできません。
同じ論理は認証トークンクッキーには当てはまりません。なぜならサーバはリクエストヘッダでそれを期待しており、攻撃者はそれを置くために特別なことをする必要がないからです。
答えとして私の最もよい推測:CSRFトークンをサーバからブラウザに転送する方法についてこれらの3つのオプションを検討してください。
最初のもの、リクエストボディ( 質問でリンクしたExpressチュートリアル で示されています)は、多種多様な状況への移植性が低いと思います。すべてのHTTPレスポンスが動的に生成されるわけではありません。トークンを生成されたレスポンスに入れる必要がある場所は、(隠れた形式の入力、JSコードの断片、または他のJSコードからアクセス可能な変数など、さまざまな場合があります) CSRFトークンを入れるため)そのため、カスタマイズは可能ですが、#1は、万能のアプローチをとるのは困難です。
2番目のカスタムヘッダーは魅力的ですが、実際には機能しません。 JSは起動したXHRのヘッダーを取得できますが、読み込んだページのヘッダーは取得できません 。
3番目のもの、つまりSet-Cookieヘッダーによって運ばれるCookieは、すべての状況で使いやすいアプローチとして残されています(誰のサーバーでも要求ごとにCookieヘッダーを設定できます。データは要求本体にあります。したがって、その欠点にもかかわらず、フレームワークを広く実装するための最も簡単な方法でした。
セッションクッキー(これは一種の標準です)以外に、私は余分なクッキーを使用したくありません。
私は、多数のAJAX要求を使用して単一ページのWebアプリケーション(SPA)を構築するときに私には有効な解決策を見つけました。注:私はサーバー側のJavaとクライアント側のJQueryを使用していますが、不思議なことではないので、この原則はすべての一般的なプログラミング言語で実装できると思います。
余分なクッキーを使わないで私の解決策は簡単です:
ログインが成功した後にサーバーから返されたCSRFトークンをグローバル変数に格納します(もちろん、グローバルなストレージの代わりにWebストレージを使用する場合)。各AJAX呼び出しでX-CSRF-TOKENヘッダーを提供するようにJQueryに指示してください。
メインの "index"ページには、この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);
}