クロスサイトリクエストフォージェリ (CSRF) 対策

revision-up-to:17812 (1.4)

CSRF ミドルウェアとテンプレートタグは、簡単に使える クロスサイトリクエストフォージェリ (Cross Site Request Forgeries) 対策を提供しています。このタイプの攻撃は、悪意のあるウェブサイトを訪れたユーザー のログイン済みの権限で、あなたのサイトに対して何らかの操作を行うことを目的とした リンクやフォームボタン、 JavaScript を設置したウェブサイトによって行われます。 また、関連する攻撃として、ユーザーを騙して別のユーザー権限でログインさせる ログイン CSRF と呼ばれる攻撃もありますが、これも含まれます。

CSRF 攻撃に対する第一の防御は、 GET (と、9.1.1 Safe Methods,HTTP 1.1, RFC 2616#section-9.1.1 で定義された ‘安全な’ メソッド) リクエストから副作用を 取り除くというものです。そして、 POST, PUT, DELETE のような、’安全でない’ メソッ ドによるリクエストについては、下記の手順に従うことで対策することができます。

使い方

CSRF 対策をあなたのビューで有効にするには、以下の手順に従ってください。:

  1. 'django.middleware.csrf.CsrfViewMiddleware' ミドルウェアを MIDDLEWARE_CLASSES に追加してください。 (このミドルウェアは、 CSRF 対策が為されていることを前提として動作するどのミドルウェアよりも前に 追加します。)

    また、代わりに 対策したい特定のビューに対して、 csrf_protect() デコレータを使用することも できます (下記参照)

  1. 自身のサイト内で POST リクエストを送るすべてのフォームの <form> タグ内 で、 csrf_token テンプレートタグを使用します。例:

    <form action="." method="post">{% csrf_token %}
    

    外部のサイトに対してリクエストを送るフォームについては使用すべきではあり ません。 CSRF トークンが流出し、脆弱性を生むからです。

  1. 対応するビューの内部で、 'django.core.context_processors.csrf' コンテキストプロセッサーを使用出来るようにします。通常、2つの方法のうち、 どちらかの方法を選択します。:

    1. RequestContext を使用する。 RequestContext は、 (TEMPLATE_CONTEXT_PROCESSORS の設定によらず) 'django.core.context_processors.csrf' を常に使用します。もし、ジェ ネリックビューや Django 付属のアプリを使用している場合は、あなたは既に この方法を使用しています。なぜなら、これらのアプリは常に RequestContext を使用しているからです。
    1. 手動でインポートし、プロセッサーを使って CSRF トークンを生成して、 テンプレートのコンテキストに追加する。例:

      from django.core.context_processors import csrf
      from django.shortcuts import render_to_response
      
      def my_view(request):
          c = {}
          c.update(csrf(request))
          # ... view code here
          return render_to_response("a_template.html", c)
      

      このような処理を行う、 render_to_response() のラッパー関数を作成しても良いかもしれません。

補助スクリプトの extras/csrf_migration_helper.py を使えば、これらの手順を 行わなければならないテンプレートやコードを自動的に見つけてくれます。 また、どうやって使えばよいかのヘルプもすべて用意されています。

AJAX

上記の手順を AJAX を用いた POST リクエストで行うのは、少し不便です。あなたは、 すべての POST リクエストについて、 CSRF トークンを POST するデータに含めることを 覚えておかなければなりません。なので、別の方法が用意されています。それは、各 XMLHttpRequest に対して、 X-CSRFToken という独自ヘッダーに CSRF トークンの 値を設定することです。多くの JavaScript のフレームワークはすべてのリクエストに ついて、ヘッダーを設定するようなフック機能を提供しているので、この操作は多くの 場合、簡単に行うことができます。 jQuery の場合、 ajaxSend イベントを以下の ように記述します:

jQuery(document).ajaxSend(function(event, xhr, settings) {
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    function sameOrigin(url) {
        // url could be relative or scheme relative or absolute
        var host = document.location.host; // host + port
        var protocol = document.location.protocol;
        var sr_origin = '//' + host;
        var origin = protocol + sr_origin;
        // Allow absolute or scheme relative URLs to same origin
        return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
            (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
            // or any other URL that isn't scheme relative or absolute i.e relative.
            !(/^(\/\/|http:|https:).*/.test(url));
    }
    function safeMethod(method) {
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }

    if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
        xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
    }
});

Note

jQuery 1.5 ではバグがあるため、上記の例は正しく動作しません。使用している jQuery のバージョンが 1.5.1 以上であることを確認してください。

これを サイトで使用される JavaScript のファイルに追加すれば、 jQuery 経由で 送信された AJAX POST リクエストは、CSRF 対策に引っかかることはなくなります。

上記のコードは、 jQuery cookie plugin を用いて、 getCookie を置き換え 、jQuery 1.5 以降で追加された settings.crossDomain を用いて sameOrigin を置き換えることに よって、さらに簡略化できます。

加えて、 csrf_token を使用して、クライアントに CSRF クッキーを送って いなかった場合、ユーザーがクッキーを受け取っていることを ensure_csrf_cookie() を用いて、保証する 必要があるかもしれません。

別のテンプレートエンジン

Django の組み込みではないテンプレートエンジンを使用する場合、テンプレートの コンテキストからトークンが取得できることを確認した上で、フォームに手動で追加して ください。

例えば、 Cheetah テンプレート言語を使用する場合、フォームは以下のコードを含む でしょう。:

<div style="display:none">
    <input type="hidden" name="csrfmiddlewaretoken" value="$csrf_token"/>
</div>

JavaScript に関しては、 取得した CSRF トークンの値を使用することによって、上記と 同じ方法で使用することができます。

デコレータメソッド

CsrfViewMiddlewre を追加して、サイト全体で対策する代わりに、まったく同じ機能 を保護が必要な特定のビューのみに持たせたいという場合には、 csrf_protect デコレータを使用することができます。ただし、 CSRF トークンを出力に埋め込むビュー と、フォームから POST されたデータを受け取るビュー (これらは、同じビューの 時も多いですが、違うビューの時もあります) の 両方で 使用しなけれなりません。

デコレータのみを使用することは 推奨されません 。なぜなら、もし、あなたが デコレータをつけ忘れてしまった場合、それがセキュリティホールを生むことになる からです。’念には念を (belt and blaces)’ の原則から両方を使用することは良いこと ですし、使用してもわずかなオーバーヘッドにしかならないでしょう。

csrf_protect(view)

CsrfViewMiddleware の CSRF 対策機能をビューに付加するデコレータ

使い方:

from django.views.decorators.csrf import csrf_protect
from django.shortcuts import render

@csrf_protect
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)

拒否されたリクエスト

受け取ったリクエストが CsrfViewMiddleware によって行われる認証に失敗した場合 、デフォルトでは、ユーザに ‘403 Forbidden’ が送信されます。これは普通、本当に CSRF が行われたか、プログラマのミスによって、 POST フォームに CSRF トークンを 含め忘れたかでない限りは起こりません。

けれども、エラーページがあまり親切でないと感じるのならば、このような場面を処理 するために、自作のビューを設定することができます。 そうするには、単に settings の CSRF_FAILURE_VIEW を設定してください。

仕組み

CSRF 対策は以下の要素からなります :

  1. ランダムな値 (いわゆる、独立一時セッション (session independent nonce) ) が設定されており、外部のサイトから参照できない CSRF クッキー。

    このクッキーは CsrfViewMiddleware によって設定されます。これは、クッキー がずっと保持されることを前提としていますが、絶対に破棄されないクッキーを作成 することは不可能なので、 django.middleware.csrf.get_token() (この関数 は、内部的に CSRF トークンを取り出すために使用されています。) が呼び出された すべてのレスポンスについて、 CSRF クッキーが送信されます。

  1. すべての POST リクエストを送信するフォームに含まれる、 ‘csrfmiddlewaretoken’ という名前を持つ hidden フィールド。この値フィールドの値には、 CSRF クッキー の値が使われます。

    この処理は、テンプレートタグによって行われます。

  1. HTTP GET, HEAD, OPTIONS, TRACE メソッドを除く、すべてのメソッドについて、 CSRF クッキーと ‘csrfmiddlewaretoken’ フィールドを保持しており、なおかつ 正しい値を保持しているリクエスト。もし、そうでないならば、ユーザーは 403 エラーを受け取ることになります。

    この確認は、 CsrfViewMiddleware によって行われます。

  1. 加えて、 HTTPS リクエストの場合は、 リファラ (referer) が CsrfViewMiddleware によって厳密にチェックされます。これは、 独立一時 セッションを用いた HTTPS 通信における中間者攻撃(Man-In-The-Middle atack) に対応する必要があるからです。(不幸にも) HTTPS を使用しているサイトにおい ても、 HTTP の’Set-Cookie’ ヘッダーが認められてしまうという事実によります。 (HTTP では、リファラヘッダの内容は十分に信頼出来ないため、リファラのチェック は行われません。)

これらの処理により、あなたのウェブサイト由来のフォームだけが POST を送り返せる ことを保証できます。

ここでは、故意に GET リクエスト (と、その他 ‘安全’ と RFC 2616 によって定義 されたリクエスト) を無視しています。 これらのリクエストは危険な副作用を持た ないはずなので、 GET リクエストを使った CSRF 攻撃は威力を持たないのです。 RFC 2616 では、POST, PUT, DELETE が ‘安全でない’ ものとして定義されており、 その他のメソッドについては、安全でないものとみなして、最大限の対策を行なって います。

キャッシュする

csrf_token がテンプレート内で使用されている (もしくは、 get_token 関数が何らかの別の方法で呼び出されていた) 場合、 CsrfViewMiddleware は、 クッキーと Vary: Cookie ヘッダをレスポンスに付加するでしょう。これは、この ミドルウェアがキャッシュの扱い方を教えれば、キャッシュを扱うミドルウェアがうまく 処理してくれることを期待することを意味しています (UpdateCacheMiddleware は 他のどのミドルウェアよりも後に置きます)。

けれども、あなたが特定のビューに対してキャッシュを設定するデコレータを使用した 場合、 CSRF ミドルウェアは、まだ Vary ヘッダーや CSRF クッキーを設定できていない はずなので、レスポンスはこれらを含まないままキャッシュされてしまいます。この 場合、 CSRF トークンを必要とするであろうビューには、すべて django.views.decorators.csrf.csrf_protect() デコレータを先に使用しておくべき です。:

from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_protect

@cache_page(60 * 15)
@csrf_protect
def my_view(request):
    # ...

テストする

CsrfViewMiddleware は、すべての POST リクエストについて CSRF トークンを 必要とするため、普通、ビュー関数のテストの大きな障害になるでしょう。そのため、 Django のテスト用の HTTP クライアントは、 このミドルウェアと csrf_protect デコレータの制限を緩めるフラグを各リクエストに付加するので、これらがリクエストを 拒否することはなくなります。その他のすべての処理 (例えば、 クッキーの送信など) は、通常と同様に働きます。

もし、何らかの理由で CSRF のチェックを 有効にしたい と考えるのであれば、 CSRF のチェックを有効にしたテストクライアントを作成することができます。:

>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)

制限

サイト内のサブドメインは、ドメイン全体に対してクッキーを設定することができる でしょう。クッキーをセットしたり、対応するトークンを使用することによって、サブ ドメインからは CSRF 対策を巧みにすり抜けることができるでしょう。この問題を回避 するための唯一の手段は、サブドメインを信用できるユーザーによって管理させる(もし くは、最低でもクッキーを設定させないようにする) ことです。特筆すべき点は、 信用出来ない人間にサブドメインを与えることは、 CSRF 以外にも、セッションフィク セーション (session fixation) のような他の脆弱性も生むため、良い案ではありま せんし、この脆弱性は現在一般に使用されているブラウザにおいて簡単に修正できるも のではないことです。

特殊な場合

ある特定のビューでは、ここで紹介した通常のパターンに当てはまらないような特殊な 使い方が必要になることがあるでしょう。これらの状況では、いくつかのユーティリティ が役に立つでしょう。これらが必要になると思われるシナリオは、以下の節に書かれて います。

ユーティリティ

csrf_exempt(view)

このデコレータは、CSRF 対策のチェックが無効化されるビューを表します。例:

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def my_view(request):
    return HttpResponse('Hello world')
requires_csrf_token(view)

通常、 csrf_token テンプレートタグは CsrfViewMiddleware.process_view や、それに準ずる csrf_protect の ようなものが実行されない限りは、動作しません。ビューに対するデコレータの requires_csrf_token は、このテンプレートタグが動作することを保証する ために使用されます。このデコレータは csrf_protect に似ていますが、到達 したリクエストを拒否することは決してありません。

例:

from django.views.decorators.csrf import requires_csrf_token
from django.shortcuts import render

@requires_csrf_token
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)
Django 1.4 で新たに登場しました: リリースノートを参照してください

このデコレータはビューに対して、強制的に CSRF クッキーを送出させます。

シナリオ

いくつかのビューでは CSRF 対策を解除しなければならない

ほとんどのビューでは CSRF 対策が必要としますが、いくつかだけはそうでない場合 です。

解決策: ミドルウェアを無効化し、必要とするビューすべてに対して csrf_protect を付加するよりも、ミドルウェアを有効にした上で csrf_exempt() を使用する方が良いでしょう。

CsrfViewMiddleware.process_view が使用されていない

ビューが実行される前に、CsrfViewMiddleware.process_view が実行されていない かもしれない場合があります。 - 例としては、404 や 500 のエラーハンドラーです。 - そのような状況でも CSRF トークンがフォーム内に必要になる場合です。

解決策: requires_csrf_token() を使用して ください。

CSRF 対策が無効化されているビューにおいて、CSRF トークンが必要になる

いくつかのビューでは csrf_exempt によって、対策が解除され、無効化されている かもしれません。そのような状況でも、 CSRF トークンが必要になる場合です。

解決策: csrf_exempt()requires_csrf_token() の後に使用してくだ さい。 (すなわち、 requires_csrf_token が最も内側のデコレータになるという ことです)

ビュー内の特定の経路で対策が必要になる

あるビューにおいて、特定の状態でのみ CSRF 対策が必要になり、なおかつ、 他の場合には CSRF 対策を行えない場合です。

解決策: csrf_exempt() をビュー全体では使用 し、対策が必要な経路にのみ csrf_protect() を 使用してください。例:

from django.views.decorators.csrf import csrf_exempt, csrf_protect

@csrf_exempt
def my_view(request):

    @csrf_protect
    def protected_path(request):
        do_something()

    if some_condition():
       return protected_path(request)
    else:
       do_something_else()

ページが HTML フォームではなく AJAX を使用している

あるページにおいて、 POST リクエストを AJAX 経由で送信し、なおかつ、ページが CSRF クッキーが送出されるために必要な csrf_token を含む HTML 上のフォ ームを持たない場合です。

解決策: そのページを生成するビューに対して、 ensure_csrf_cookie() を使用してください。

組み込みアプリと再利用されるアプリ

開発者は CsrfViewMiddleware を無効化することができるので、組み込みアプリ のうち、 CSRF に対する安全性を保証する必要があるすべてのアプリには csrf_protect デコレータが使用されています。他の場面で再利用されるアプリを 作成する開発者は、これと同じ保証が必要なビューに対しては csrf_protect デコ レータを使用することが推奨されています。

設定

Django の CSRF の動作を調整するためのいくつかの設定があります。

CSRF_FAILURE_VIEW

Django 1.2 で新たに登場しました: リリースノートを参照してください

デフォルト値: 'django.views.csrf.csrf_failure'

到達したリクエストが CSRF 対策によって拒否された場合に使用されるビュー関数への ドット区切りのパスです。 ビュー関数は以下の引数を取ります:

def csrf_failure(request, reason="")

reason は、リクエストが拒否された理由を表す短いメッセージです。 (開発者や ログをとるのためのもので、一般のユーザーのためのものではありません)