クロスサイトリクエストフォージェリ保護—Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.2.x/ref/csrf
移動先:案内検索

クロスサイトリクエストフォージェリ保護

CSRFミドルウェアとテンプレートタグは、クロスサイトリクエストフォージェリに対する使いやすい保護を提供します。 このタイプの攻撃は、悪意のあるWebサイトに、ブラウザで悪意のあるサイトにアクセスするログインユーザーの資格情報を使用して、Webサイトで何らかのアクションを実行することを目的としたリンク、フォームボタン、またはJavaScriptが含まれている場合に発生します。 関連するタイプの攻撃である「ログインCSRF」もカバーされています。この攻撃サイトは、ユーザーのブラウザをだまして、他の誰かの資格情報を使用してサイトにログインさせます。

CSRF攻撃に対する最初の防御策は、GETリクエスト(および RFC 7231#section-4.2.1 で定義されている他の「安全な」メソッド)に副作用がないことを確認することです。 POST、PUT、DELETEなどの「安全でない」メソッドを介したリクエストは、以下の手順に従って保護できます。

それを使用する方法

ビューでCSRF保護を利用するには、次の手順に従います。

  1. CSRFミドルウェアは、:setting: `MIDDLEWARE` 設定でデフォルトでアクティブ化されます。 この設定を上書きする場合は、'django.middleware.csrf.CsrfViewMiddleware'が、CSRF攻撃が処理されたと想定するビューミドルウェアの前に来る必要があることに注意してください。

    無効にした場合(これは推奨されません)、保護する特定のビューで csrf_protect()を使用できます(以下を参照)。

  2. POSTフォームを使用するテンプレートで、フォームが内部URL用である場合は、<form>要素内で:ttag: `csrf_token` タグを使用します。

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

    外部URLをターゲットとするPOSTフォームでは、これを実行しないでください。CSRFトークンがリークされ、脆弱性が発生する可能性があります。

  3. 対応するビュー関数で、 RequestContext を使用して応答をレンダリングし、{% csrf_token %}が正しく機能するようにします。 render()関数、ジェネリックビュー、またはcontribアプリを使用している場合、これらはすべてRequestContextを使用しているため、すでに説明されています。

AJAX

上記の方法はAJAXPOSTリクエストに使用できますが、いくつかの不便があります。すべてのPOSTリクエストでCSRFトークンをPOSTデータとして渡すことを忘れないでください。 このため、別の方法があります。各XMLHttpRequestで、カスタムX-CSRFTokenヘッダー(:setting: `CSRF_HEADER_NAME` 設定で指定)をCSRFトークンの値に設定します。 。 多くのJavaScriptフレームワークには、リクエストごとにヘッダーを設定できるフックが用意されているため、これは多くの場合簡単です。

まず、CSRFトークンを取得する必要があります。 これを行う方法は、:setting: `CSRF_USE_SESSIONS` および:setting:` CSRF_COOKIE_HTTPONLY` 設定が有効になっているかどうかによって異なります。

AJAXリクエストにトークンを設定する

最後に、AJAXリクエストにヘッダーを設定する必要があります。 fetch() APIの使用:

const request = new Request(
    /* URL */,
    {headers: {'X-CSRFToken': csrftoken}}
);
fetch(request, {
    method: 'POST',
    mode: 'same-origin'  // Do not send CSRF token to another domain.
}).then(function(response) {
    // ...
});

Jinja2テンプレートでのCSRFの使用

Djangoの Jinja2 テンプレートバックエンドは、すべてのテンプレートのコンテキストにテンプレート:Csrf inputを追加します。これは、Djangoテンプレート言語の{% csrf_token %}と同等です。 例えば:

<form method="post">{{ csrf_input }}

デコレータ方式

CsrfViewMiddlewareを包括的保護として追加するのではなく、保護が必要な特定のビューで、まったく同じ機能を持つcsrf_protectデコレータを使用できます。 CSRFトークンを出力に挿入するビュー、およびPOSTフォームデータを受け入れるビューで両方を使用する必要があります。 (これらは多くの場合同じビュー機能ですが、常にではありません)。

デコレータを単独で使用することは推奨されません。使用し忘れるとセキュリティホールが発生するためです。 両方を使用する「ベルトとブレース」戦略は問題なく、最小限のオーバーヘッドしか発生しません。

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)

クラスベースのビューを使用している場合は、クラスベースのビューの装飾を参照できます。


拒否されたリクエスト

デフォルトでは、着信リクエストがCsrfViewMiddlewareによって実行されたチェックに失敗した場合、「403Forbidden」応答がユーザーに送信されます。 これは通常、本物のクロスサイトリクエストフォージェリがある場合、またはプログラミングエラーのためにCSRFトークンがPOSTフォームに含まれていない場合にのみ表示されます。

ただし、エラーページはあまりわかりにくいため、この状態を処理するための独自のビューを提供することをお勧めします。 これを行うには、:setting: `CSRF_FAILURE_VIEW` 設定を設定します。

CSRFの失敗は、警告として django.security.csrf ロガーに記録されます。


使い方

CSRF保護は、次のことに基づいています。

  1. 他のサイトがアクセスできないランダムなシークレット値に基づくCSRFCookie。

    このCookieはCsrfViewMiddlewareによって設定されます。 リクエストでまだ設定されていない場合は、django.middleware.csrf.get_token()(CSRFトークンを取得するために内部的に使用される関数)を呼び出したすべての応答とともに送信されます。

    BREACH 攻撃から保護するために、トークンは単なる秘密ではありません。 ランダムマスクがシークレットの前に付加され、それをスクランブルするために使用されます。

    セキュリティ上の理由から、シークレットの値はユーザーがログインするたびに変更されます。

  2. すべての送信POSTフォームに存在する「csrfmiddlewaretoken」という名前の非表示のフォームフィールド。 このフィールドの値は、ここでもシークレットの値であり、マスクが追加され、スクランブルに使用されます。 get_token()を呼び出すたびにマスクが再生成されるため、そのような応答ごとにフォームフィールドの値が変更されます。

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

  3. HTTP GET、HEAD、OPTIONS、またはTRACEを使用していないすべての着信要求には、CSRF Cookieが存在し、「csrfmiddlewaretoken」フィールドが存在し、正しい必要があります。 そうでない場合、ユーザーは403エラーを受け取ります。

    'csrfmiddlewaretoken'フィールド値を検証する場合、完全なトークンではなく、シークレットのみがCookie値のシークレットと比較されます。 これにより、常に変化するトークンを使用できます。 各リクエストは独自のトークンを使用できますが、秘密はすべての人に共通のままです。

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

  4. さらに、HTTPSリクエストの場合、厳密なリファラーチェックはCsrfViewMiddlewareによって実行されます。 つまり、サブドメインがドメインのCookieを設定または変更できる場合でも、そのリクエストは自分の正確なドメインから送信されないため、ユーザーにアプリケーションへの投稿を強制することはできません。

    これは、セッションに依存しないシークレットを使用する場合にHTTPSで発生する可能性のある中間者攻撃にも対処します。これは、HTTP Set-Cookieヘッダーが(残念ながら)クライアントと通信している場合でも受け入れられるためです。 HTTPS下のサイト。 (Refererヘッダーの存在はHTTPで十分な信頼性がないため、HTTP要求のリファラーチェックは行われません。)

    :setting: `CSRF_COOKIE_DOMAIN` 設定が設定されている場合、リファラーはそれと比較されます。 先頭にドットを含めることで、サブドメイン間のリクエストを許可できます。 たとえば、CSRF_COOKIE_DOMAIN = '.example.com'は、www.example.comおよびapi.example.comからのPOST要求を許可します。 設定が設定されていない場合、リファラーはHTTP Hostヘッダーと一致する必要があります。

    現在のホストまたはCookieドメインを超えて受け入れられたリファラーを拡張するには、:setting: `CSRF_TRUSTED_ORIGINS` 設定を使用します。

これにより、信頼できるドメインから発信されたフォームのみを使用してデータをPOSTバックできます。

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

CSRF保護では中間者攻撃から保護できないため、 HTTPSHTTP Strict Transport Security を使用してください。 また、 HOSTヘッダーの検証、およびサイトにクロスサイトスクリプティングの脆弱性がないことも前提としています(XSSの脆弱性により、攻撃者はCSRFの脆弱性で許可されていることをすべて実行できます。ずっと悪いです)。

Refererヘッダーの削除

サードパーティのサイトへのリファラーURLの開示を回避するには、サイトの<a>タグでリファラー無効にすることをお勧めします。 たとえば、<meta name="referrer" content="no-referrer">タグを使用したり、Referrer-Policy: no-referrerヘッダーを含めたりできます。 HTTPSリクエストに対するCSRF保護の厳密なリファラーチェックにより、これらの手法により、「安全でない」メソッドを使用したリクエストでCSRFエラーが発生します。 代わりに、サードパーティのサイトへのリンクに<a rel="noreferrer" ...>"などの代替手段を使用してください。


キャッシング

:ttag: `csrf_token` テンプレートタグがテンプレートによって使用されている場合(またはget_token関数が別の方法で呼び出されている場合)、CsrfViewMiddlewareはCookieと応答へのVary: Cookieヘッダー。 これは、ミドルウェアが指示どおりに使用された場合、キャッシュミドルウェアとうまく連携することを意味します(UpdateCacheMiddlewareは他のすべてのミドルウェアよりも優先されます)。

ただし、個々のビューでキャッシュデコレータを使用する場合、CSRFミドルウェアはまだVaryヘッダーまたはCSRF Cookieを設定できず、応答はどちらも使用せずにキャッシュされます。 この場合、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)

制限事項

サイト内のサブドメインは、ドメイン全体のクライアントにCookieを設定できます。 Cookieを設定し、対応するトークンを使用することで、サブドメインはCSRF保護を回避できます。 これを回避する唯一の方法は、サブドメインが信頼できるユーザーによって制御されていることを確認することです(または、少なくともCookieを設定できません)。 CSRFがなくても、セッション固定など、信頼できないパーティにサブドメインを与えることをお勧めしない脆弱性が他にもあり、これらの脆弱性は現在のブラウザでは簡単に修正できないことに注意してください。


エッジケース

特定のビューには、ここで想定されている通常のパターンに適合しないことを意味する異常な要件がある場合があります。 このような状況では、多くのユーティリティが役立ちます。 それらが必要になる可能性のあるシナリオについては、次のセクションで説明します。

ユーティリティ

以下の例では、関数ベースのビューを使用していることを前提としています。 クラスベースのビューを使用している場合は、クラスベースのビューの装飾を参照できます。

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')
requires_csrf_token(view)

通常、:ttag: `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)
ensure_csrf_cookie(view)
このデコレータは、ビューにCSRFCookieを送信するように強制します。


シナリオ

CSRF保護は、いくつかのビューで無効にする必要があります

ほとんどのビューにはCSRF保護が必要ですが、必要ないものもあります。

解決策:ミドルウェアを無効にして、それを必要とするすべてのビューにcsrf_protectを適用するのではなく、ミドルウェアを有効にして csrf_exempt()を使用します。


CsrfViewMiddleware.process_viewは使用されていません

ビューが実行される前にCsrfViewMiddleware.process_viewが実行されなかった場合があります(たとえば、404ハンドラーと500ハンドラー)が、フォームにCSRFトークンが必要です。

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


保護されていないビューにはCSRFトークンが必要です

保護されておらず、csrf_exemptによって免除されているビューがいくつかある可能性がありますが、それでもCSRFトークンを含める必要があります。

解決策: csrf_exempt()に続いて require_csrf_token()を使用します。 (NS requires_csrf_tokenは最も内側のデコレータである必要があります)。


ビューには1つのパスの保護が必要です

ビューは、1セットの条件下でのみ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を使用します

ページがAJAXを介してPOSTリクエストを行い、ページに:ttag: `csrf_token` を含むHTMLフォームがないために必要なCSRFCookieが送信されます。

解決策:ページを送信するビューで sure_csrf_cookie()を使用します。


貢献して再利用可能なアプリ

開発者はCsrfViewMiddlewareをオフにすることができるため、contribアプリの関連するすべてのビューは、csrf_protectデコレータを使用して、CSRFに対するこれらのアプリケーションのセキュリティを確保します。 同じ保証が必要な他の再利用可能なアプリの開発者も、ビューでcsrf_protectデコレータを使用することをお勧めします。


よくある質問

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

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

一部のセキュリティ監査ツールはこれを問題としてフラグを立てますが、前述のように、攻撃者はユーザーのブラウザのCSRFCookieを盗むことはできません。 Firebug、Chrome開発ツールなどを使用して独自のトークンを「盗む」または変更する。 脆弱性ではありません。


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

いいえ、これは仕様によるものです。 CSRF保護をセッションにリンクしないと、セッションを持たない匿名ユーザーからの送信を許可する Pastebin などのサイトで保護を使用できます。

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


ログイン後にユーザーがCSRF検証の失敗に遭遇するのはなぜですか?

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