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

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

CSRF 攻撃に対する第一の防御は、 GET リクエスト (および RFC 9110#section-9.2.1 で定義された ‘安全な’ メソッド) から副作用を取り除くというものです。そして、 POST、PUT、DELETE のような、’安全でない’ メソッドによるリクエストについては、 Django の CSRF 保護を利用する で説明されている手順で保護できます。

しくみ

CSRF対策は以下のようなことを基本としています:

  1. 他のサイトがアクセスできないランダムな秘密の値である CSRF クッキー。

    CsrfViewMiddlewaredjango.middleware.csrf.get_token() が呼び出されると、常にこのクッキーをレスポンスと一緒に送信します。その他の場合にも送信できます。セキュリティ上の理由から、secret の値はユーザがログインするたびに変更されます。

  2. "csrfmiddlewaretoken" と名付けられた、すべての送信 POST フォームに存在する隠しフォームフィールド。

    BREACH 攻撃から守るために、このフィールドの値は単なる秘密ではありません。マスクを使って、レスポンスごとに異なるスクランブルをかけます。マスクは get_token() を呼び出すたびにランダムに生成されるので、フォームフィールドの値は毎回異なります。

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

  3. HTTP GET、HEAD、OPTIONS、または TRACE を使用していないすべての受信リクエストについて、CSRF クッキーが存在し、"csrfmiddlewaretoken" フィールドが正しく存在する必要があります。存在しない場合、ユーザは 403 エラーを受け取ります。

    "csrfmiddlewaretoken" フィールドの値を検証するとき、完全なトークンではなくシークレットだけがクッキー値のシークレットと比較されます。これにより、常に変化するトークンを使うことができます。各リクエストは固有のトークンを使うかもしれませんが、シークレットはすべてのリクエストに共通のままです。

    このチェックは CsrfViewMiddleware によって行われます。

  4. CsrfViewMiddleware はブラウザから提供された場合、現在のホストと CSRF_TRUSTED_ORIGINS 設定に対して Origin header を検証します。これにより、クロスサブドメイン攻撃から保護されます。

  5. さらに、HTTPS リクエストの場合、Origin ヘッダーが提供されない場合には、CsrfViewMiddleware は厳密なリファラチェックを実行します。これは、サブドメインがあなたのドメインに対してクッキーを設定または変更できるとしても、そのリクエストがあなた自身の正確なドメインから来ない限り、ユーザーがあなたのアプリケーションに対して投稿を強制することはできないことを意味します。

    これはまた、セッションに依存しない秘密を使用するときに HTTPS で可能な中間者攻撃 (man-in-the-middle attack) に対処するためでもあります。 HTTP の Set-Cookie ヘッダは (残念ながら) クライアントが HTTPS でサイトと通信しているときでも受け付けられます。 (Referer ヘッダーの存在が HTTP 下では十分に信頼できないため、HTTP リクエストに対してはリファラチェックは行われません。)

    もし CSRF_COOKIE_DOMAIN 設定が設定されていれば、リファラはその設定と比較されます。先頭のドットを指定することで、クロスサブドメインリクエストを許可できます。たとえば、CSRF_COOKIE_DOMAIN = '.example.com'www.example.comapi.example.com からの POST リクエストを許可します。この設定が設定されていない場合、referer は HTTP の Host ヘッダと一致しなければなりません。

    CSRF_TRUSTED_ORIGINS 設定により、現在のホストやクッキーのドメインを超えてリファラを拡張できます。

これにより、信頼できるドメインから送信されたフォームだけが、データを POST するために使用できるようになります。

GET リクエスト(および RFC 9110#section-9.2.1 によって「安全」と定義されている他のリクエスト)を意図的に無視します。これらのリクエストは決して潜在的に危険な副作用を持つべきではなく、したがって GET リクエストを使用した CSRF 攻撃は無害であるべきです。 RFC 9110#section-9.2.1 は POST、PUT、DELETE を「安全でない」と定義し、最大限の保護のためには、他のすべてのメソッドも安全でないとみなされます。

CSRF保護は中間者攻撃に対して保護することはできませんので、HTTPSHTTP Strict Transport Security と共に使用してください。また、HOSTヘッダーの検証 が行われていること、およびサイト上に クロスサイトスクリプティングの脆弱性 が存在しないことを前提としています(なぜなら、XSSの脆弱性は既に攻撃者がCSRFの脆弱性を利用して行えること、そしてそれ以上のことを可能にするからです)。

リファラヘッダーの削除

サードパーティのサイトにリファラURLを公開しないようにするには、自分のサイトの <a> タグでリファラを無効にするとよいでしょう ( disable the referer ) 。たとえば、 <meta name="referrer" content="no-referrer"> タグを使うか、 Referrer-Policy: no-referrer ヘッダを含めます。CSRF による HTTPS リクエストの厳格な参照元チェックにより、これらのテクニックは「安全でない」メソッドを含むリクエストで CSRF 失敗の原因となります。代わりに、サードパーティのサイトへのリンクには <a rel="noreferrer" ...>" のような代替手段を使用してください。

制限事項

サイト内のサブドメインは、ドメイン全体のクライアントにクッキーを設定できます。クッキーを設定し、対応するトークンを使うことで、サブドメインは CSRF 防御を回避できます。これを避ける唯一の方法は、サブドメインが信頼されたユーザによって管理される(あるいは、少なくともクッキーを設定できない)ようにすることです。CSRF がなくても、セッションの固定化など、サブドメインを信頼できない相手に与えることを悪用する脆弱性は他にもあり、これらの脆弱性は現在のブラウザでは簡単に修正できないことに注意してください。

ユーティリティ

以下の例では、関数ベースのビューを使っていることを想定しています。クラスベースのビューを使っている場合は、Decorating class-based views を参照してください。

csrf_exempt(view)

このデコレータは、ビューがミドルウェアによる保護の対象外であることを示します。例:

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt


@csrf_exempt
def my_view(request):
    return HttpResponse("Hello world")
Changed in Django 5.0:

非同期ビュー関数のラップをサポートしました。

csrf_protect(view)

ビューに対する CsrfViewMiddleware の保護を提供するデコレータです。

使い方は以下の通りです:

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


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

非同期ビュー関数のラップをサポートしました。

requires_csrf_token(view)

通常 csrf_token テンプレートタグは CsrfViewMiddleware.process_view または csrf_protect のような等価なものが実行されていないと動作しません。ビューデコレータ requires_csrf_token を使用すると、テンプレートタグを確実に動作させることができます。このデコレータは csrf_protect と同様に動作しますが、リクエストを拒否することはありません。

実装例:

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


@requires_csrf_token
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)
Changed in Django 5.0:

非同期ビュー関数のラップをサポートしました。

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

Changed in Django 5.0:

非同期ビュー関数のラップをサポートしました。

よくある質問

任意の CSRF トークンのペア(クッキーと POST データ)を投稿するのは脆弱性ではないですか?

いいえ、これは設計によるものです。中間者攻撃がなければ、攻撃者が CSRF トークン・クッキーを被害者のブラウザに送信する方法はありません。したがって、攻撃を成功させるには、XSS などによって被害者のブラウザのクッキーを取得する必要がありますが、その場合、攻撃者は通常 CSRF 攻撃を必要としません。

セキュリティ監査ツールの中には、これを問題視するものもありますが、前述のように、攻撃者はユーザのブラウザの CSRF クッキーを盗むことはできません。FirebugやChromeの開発ツールなどを使って 自分自身 のトークンを「盗んだり」修正したりすることは脆弱性ではありません。

Django の CSRF 防御がデフォルトでセッションにリンクされていないのは問題ではないですか?

いいえ、これは設計によるものです。CSRF 防御をセッションにリンクしないことで、 pastebin のような、セッションを持たない匿名ユーザからの投稿を許可するサイトで保護を使用できます。

CSRF トークンをユーザのセッションに保存したい場合は、 CSRF_USE_SESSIONS 設定を使用します。

ログイン後に CSRF 検証に失敗するのはなぜですか?

セキュリティ上の理由から、CSRF トークンはユーザがログインするたびにローテーションされます。ログイン前にフォームが生成されたページは、古くて無効な CSRF トークンを持つことになり、再読み込みが必要になります。これは、ユーザがログイン後に戻るボタンを使用した場合や、別のブラウザのタブでログインした場合に発生することがあります。

Back to Top