このページでは、Django 利用者向けに CSRF 対策について解説していきます。
既に Django のチュートリアルや参考書等でフォームを作成した経験のある方の中には、{% csrf_token %}
というテンプレートタグを利用した方も多いのではないかと思います。
この {% csrf_token %}
はウェブアプリに対して CSRF 対策を行うために必要なテンプレートタグとなります。
では、なぜ {% csrf_token %}
で CSRF 対策をすることができるのでしょうか?
この辺りも踏まえながら、Django における CSRF 対策について解説していきたいと思います!
Contents
Django における CSRF 対策
まずは、Django における CSRF 対策について解説していきます。
Django における CSRF 対策の仕組み
Django で開発するウェブアプリでは、CSRF 検証を実施することによって CSRF 対策が行われることになります。この CSRF 検証では CSRF トークンの有効性の判断が行われます。
後述の CSRF 対策の必要性 で詳しく解説しますが、CSRF トークンとは「悪意ある攻撃者からの攻撃を防ぐためのワンタイムパスワード」みたいなものになります。
そして、その CSRF トークンが有効であるかどうかを判断する機能が CSRF 検証となります。
例外を除いて送信フォームからのリクエストを拒否する
Django で開発するウェブアプリにおいては、CSRF 検証を有効化している場合、“例外を除く” 全ての「送信フォームからのリクエスト」を拒否するようになっています(後述でも解説しますが、Django ではデフォルト設定で CSRF 検証が有効化されています)。
後述の CSRF 対策の必要性 でも解説しますが、送信フォームからのリクエストは CSRF 攻撃である可能性があり、全てのリクエストを受諾してしまうのは危険です。そのため、CSRF 攻撃に対する対策として、基本的には全て拒否するようになっています。
ちなみに、送信フォームからのリクエストとは、下の図のような送信フォームで「送信」ボタン等のボタンを押下することでウェブアプリに送信されるリクエストになります。このリクエストでは送信フォームのエントリー等でユーザーが入力した情報がウェブアプリに送信されます。
このページでは「送信フォームからのリクエスト」で説明を行いますが、より正確には HTTP のメソッドが POST や PUT 等のような「データを一緒に送付するリクエスト」を受信した場合に、ウェブアプリで CSRF 検証が行われることになります。
有効な CSRF トークンが送信されてきた場合のみ受諾する
ですが、送信フォームからのリクエストを本当に全て拒否してしまうと、ウェブアプリで送信フォームを利用できないことになってしまいます。例えばユーザー登録などは送信フォームからユーザーの情報を送信することで実現されることが多いのですが、これらも実現できないことになってしまいます(送信フォームからのリクエストが全て拒否されるため)。
ですので、一部のリクエストに関しては CSRF 検証で拒否されないようになっています。
そのリクエストとは、有効な CSRF トークンが含まれるリクエストとなります。有効な CSRF トークンとは、具体的には、そのウェブアプリ自身が発行した CSRF トークンとなります。
つまり、Django で開発したウェブアプリでは、送信フォームからのリクエストが送信されてきた際、そのリクエストに含まれる CSRF トークンの検証が行われ、その CSRF トークンが自身が発行したものである場合のみ、そのリクエストを受諾するようにしています。
厳密に言えば、CSRF トークンそのものの検証だけでなく、CSRF トークンを送信してきたクライアントが妥当であるかどうかの検証も行われますが、その点についての説明は、本ページでは省略しています
それ以外の場合や、CSRF トークンがそもそも送信されてこなかった場合は、リクエストを拒否するようになっています。
CSRF トークンを受け取る流れを実現する
そのため、Django で開発するウェブアプリにおいても送信フォームを扱うことは可能です。
ですが、自身で用意した送信フォームからのリクエストが拒否されないよう、まずはウェブアプリが CSRF トークンを発行するようにする必要があります。そして、送信フォームを表示するページをクライアントに送信する際、その CSRF トークンも一緒に送信する必要があります。さらに、送信フォームからのリクエストをクライアントが送信する際に、CSRF トークンを送り返してもらう必要があります。
このように、ウェブアプリが CSRF トークンを発行し、フォーム送信によるリクエスト時にその CSRF トークンを送り返してもらうような流れを実現することができれば、ウェブアプリで送信用フォームを扱うことができるようになります(送信フォームからのリクエストが受諾できるようになる)。
前述の通り、Django においては CSRF 検証を有効化している場合、送信フォームからのリクエストを受け取った際に自動的に CSRF 検証が行われるようになっています。また、CSRF トークンを埋め込んだフォームをクライアントに送信するようにすれば、クライアントから送信フォームからのリクエストが送信される際に埋め込まれた CSRF トークンも一緒に送付されるようになります。
ただし、CSRF トークンの発行は自動的には行われないようになっているため、CSRF トークンが発行されるようにウェブアプリの実装を行う必要があります。また、その CSRF トークンは送信フォームに埋め込むように実装する必要もあります。
つまり、Django でウェブアプリを開発し、さらにそのウェブアプリで送信フォームを扱いたい場合は、CSRF トークンの発行と、その CSRF トークンの送信フォームへの埋め込みが必要となります。
ここまでをまとめると、Django で開発するウェブアプリでは CSRF 検証が行われ、この検証により、基本的には送信フォームからのリクエストが拒否されるようになっています。これが Django での CSRF 対策となります。
この CSRF 検証では、自身のウェブアプリが発行した CSRF トークンが含まれる場合のみ、送信フォームからのリクエストが受諾されるようになっています。ウェブアプリで送信フォームを扱うためには、ウェブアプリが CSRF トークンを発行し、そのトークンをフォームに埋め込んだ上でクライアントに送信するよう実装する必要があります。
スポンサーリンク
Django での CSRF トークンの発行方法
では、Django において、CSRF トークンの発行やフォームへの埋め込みを実装するためにはウェブアプリをどのように実装すれば良いのでしょうか?次は、この点について解説していきたいと思います。
テンプレートタグ {% csrf_token %}
を利用する
Django においては、CSRF トークンの発行や CSRF トークンのフォームへの埋め込みは非常に簡単に行うことができます。これらは、ページの冒頭でも少し触れた {% csrf_token %}
を利用することで実現できます。
具体的には、「テンプレートファイルにおける <form>
から </form>
の間に {% csrf_token %}
のテンプレートタグを記述する」を行えば良いだけになります。
送信フォームからのリクエスト時に CSRF トークンをクライアントから一緒に送信してもらうためには、送信してもらいたい CSRF トークンをフォームに埋め込んでおく必要があります。そのため、フォームを作成するためのタグである <form>
から </form>
の間に {% csrf_token %}
のテンプレートタグを記述する必要があります。
例えば、下記はユーザー名とパスワードの送信を行うフォームに CSRF トークンを発行して埋め込む例となります。
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
<p>
<label for="id_username">ユーザー名:</label>
<input type="text" name="username" equired id="id_username">
</p>
<p>
<label for="id_password">パスワード:</label>
<input type="password" name="password" required id="id_password">
</p>
<p><input type="submit" value="送信"></p>
</form>
{% csrf_token %}
の意味
では、この {% csrf_token %}
はどのような意味のあるテンプレートタグなのでしょうか?
実は、{% csrf_token %}
のテンプレートタグが存在してもしなくても、ページとして表示される結果は同じとなります。
例えば、先ほど示したフォームの例をページとして表示した場合、下の図のようにページに表示されるのは入力エントリーとボタンのみとなります。
表示結果は同じになるのですが、{% csrf_token %}
の記述の有無によってテンプレートファイルから生成される HTML としては大きな違いがあります。
まず、テンプレートファイルに {% csrf_token %}
が記述されている場合、このテンプレートファイルからウェブアプリが HTML を生成する際に CSRF トークンの発行が行われます。さらに、{% csrf_token %}
部分が下記のような input
タグに変換されることになります。
<input name="csrfmiddlewaretoken" type="hidden" value="ylro9y8TcfvFNegkRkTWSW57gELopFMNr1UIREdJmtZy6j5dHcewgvjID51UiSbS" />
name="csrfmiddlewaretoken"
の部分が CSRF トークンであることを示す属性となっており、さらに value
属性の値はウェブアプリによって発行された CSRF トークンとなります。type="hidden"
属性が指定されているため、ページに表示はされません。
このように、テンプレートファイルに {% csrf_token %}
と記述しておけば、テンプレートファイルから HTML を生成する際にウェブアプリが CSRF トークンを発行し、その CSRF トークンを value
属性の値とする上記のような input
タグに自動的に変換されることになります(この value
属性の値、すなわち、ウェブアプリが発行 CSRF トークンは毎回異なるものとなります)。
さらに、<form>
から </form>
の間に {% csrf_token %}
を記述しておけば、その input
タグがフォーム内に埋め込まれ、その送信フォームからのリクエストに他の入力した内容と一緒に CSRF トークン(value
属性の値)も送信されるようになります。
つまり、Django においては、CSRF トークンの発行と CSRF トークンのフォームへの埋め込みは、「テンプレートファイルにおける <form>
から </form>
の間に {% csrf_token %}
のテンプレートタグを記述する」だけで実現することができます。
これに対し、<form>
から </form>
の間に {% csrf_token %}
を記述しなかった場合、送信フォームに CSRF トークンが埋め込まれないため、送信フォームからのリクエストには CSRF トークンが含まれないことになります。そのため、そのリクエストは CSRF 検証時に必ず NG 判定されることになります。
スポンサーリンク
Django での CSRF 検証の流れ
さて、フォームからリクエストが送信されると、次はウェブアプリがそのリクエストを受け取ることになります。
そして、そのリクエストに対し CSRF 検証が行われます。前述の通り、この CSRF 検証は、Django で開発したウェブアプリの場合、CSRF 検証機能が有効化されていれば自動的に行われる処理となります。したがって、Django でウェブアプリを開発する場合は、CSRF 検証機能を動作させるような実装を行う必要はありません。
この検証では、受け取った CSRF トークンが自身の発行した CSRF トークンとして有効であるかどうかを判断します。有効である場合は、送信されてきたリクエストを受諾し、受信したリクエストに基づいた処理を実行しますが、有効でない場合、例えば自身が発行していない CSRF トークンである場合や CSRF トークンが送信されてきていない場合は、送信されてきたリクエストを拒否します。
リクエストが拒否された場合は、下の図のようなエラーページの HTML がクライアントに対して送信されることになります。
このように、CSRF トークンの検証を実施することで、不正な CSRF トークンを受け取った際にリクエストを拒否することができるようになります。ただ、これだけ聞いてもメリットがよく分からないという方も多いのではないかと思いますので、後述の CSRF 対策の必要性 で CSRF 対策のメリットをもう少し詳しく説明したいと思います。
前述の通り、テンプレートファイルに {% csrf_token %}
を記述しなかった場合、そもそも {% csrf_token %}
が存在しないので CSRF トークンが発行されず、CSRF 検証を有効化している場合、フォームからの送信は必ず失敗することになります。
自身が開発しているウェブアプリの動作確認を行う際、下記のようなメッセージが表示された場合は、まずは {% csrf_token %}
の記述漏れがないかどうかを確認することをオススメします。
CSRF検証に失敗したため、リクエストは中断されました。
CSRF verification failed. Request aborted.
CSRF 検証の無効化
また、この CSRF 検証はデフォルトで有効化されていますが、設定を変更することで無効化することも可能です。
Django においては、CSRF 検証の機能は django.middleware.csrf.CsrfViewMiddleware
というミドルウェアによって提供されることになります。
さらに、Django ではプロジェクトの設定ファイルである settings.py
によって使用するミドルウェアを登録することができます。この使用するミドルウェアとして django.middleware.csrf.CsrfViewMiddleware
はデフォルトで登録されているのですが、この登録を外すことで CSRF 検証を無効化することができます。
例えば下記のように settings.py
の MIDDLEWARE
の設定部分でコメントアウトを行えば、CSRF 検証が行われなくなり、CSRF トークンの発行を行わなくてもフォーム送信を受け取ることができるようになります。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ただし、CSRF 検証はウェブアプリのセキュリティを高めるための機能であり、これを無効化するとウェブアプリのセキュリティが低下することになります。そのため、他の手段で CSRF 対策を行わない限りは、基本的には CSRF 検証はデフォルト設定のまま有効化しておくことをオススメします。
CSRF 対策の必要性
ここまで、CSRF トークンの発行方法や CSRF 検証の流れを踏まえて Django における CSRF 対策について解説してきました。
Django において、どのような仕組みで CSRF 対策が行われているのかについては理解していただけたのではないかと思いますが、そもそも、なぜ CSRF 対策は行う必要があるのでしょうか?
次は、CSRF 攻撃の例を示し、それらを踏まえて CSRF 対策の必要性について説明していきたいと思います。
前述でも少し触れましたが、CSRF トークンとは、簡単に言えば悪意ある攻撃者からの攻撃を防ぐためのワンタイムパスワードとなります。
また、ウェブアプリにおいては、ユーザーからのリクエストを送信フォームにより受け付けるようなものが多く存在します。例えば、掲示板アプリであればコメントをフォームから送信して投稿したり、ショッピングアプリであれば購入する商品や発送先をフォームから送信して商品の購入を行なったりすることになります。
このフォーム送信によるリクエストを、ウェブアプリ自身が生成したページ以外からは「拒否」することができるという点が、CSRF トークンや CSRF 検証を利用する最大のメリットになります。
例えば、掲示板アプリの例で考えてみましょう!
この掲示板アプリでは、ユーザーがコメント送信フォームからコメントを入力し、送信ボタンを押すことでコメントの投稿ができるようになっているものとします。
ここで、悪意ある人物が、この掲示板アプリのコメント送信フォームを真似たページを作成したとしましょう。このコメント送信フォームでは、送信ボタンが押された際にコメントの入力内容が無視され、犯罪予告文が掲示板アプリに投稿されるようになっているとします。
もし、あなたが間違って or なんらかの方法で誘導されてこのページに訪れて「こんにちは」とコメントを入力して「送信」ボタンを押してしまうと、犯罪予告文が投稿されてしまうことになります。
この場合、あなたが犯罪予告文を投稿したことになり、あなたは罪に問われるかもしれません…。また、あなたがユーザーではなくウェブアプリ開発者だとすれば、開発者として責任を取らされる可能性もあります。
このような、攻撃用のページを用意し、ユーザーに意図しないリクエストを送信させるような攻撃を CSRF(クロスサイトリクエストフォージェリ)と呼びます。
そして、この CSRF に対する対策が「CSRF トークンの発行」と「CSRF 検証」となります。これらを利用すれば、上記のような不正な投稿のリクエストを拒否することができます。
まず、他の悪意ある人物が、あなたのウェブアプリのページを真似て送信フォームを作成したとしても、そのフォームから送信されたリクエストに CSRF トークンが含まれない場合、CSRF 検証時に不正であると判断されてリクエストを拒否することができます。
また、そのフォームから送信されたリクエストに CSRF トークンが含まれていたとしても、そのリクエストも拒否することができます。なぜなら、その CSRF トークンはあなたのウェブアプリが発行したものではなく、悪意ある人物が発行したものだからです。
これらに対し、あなたのウェブアプリが送信フォームを表示するページを生成する際に CSRF トークンを発行してフォームに埋め込んでおけば、その送信フォームからのリクエストは拒否することなく受諾することができます。なぜなら、その CSRF トークンは、そのウェブアプリ自身が発行したものだからです。
このように、CSR トークンや CSRF 検証を利用することで、ウェブアプリ自身が生成した送信フォームからのリクエストのみを受諾し、攻撃用のページのような “そのウェブアプリ以外” から生成された送信フォームからのリクエストは全て拒否することができるようになります。
ウェブアプリ開発時や自身でウェブアプリの動作確認を行っている際には、<form>
から </form>
の間に {% csrf_token %}
さえ記述しておけば当たり前のように CSRF 検証に成功するので、CSRF トークンや CSRF 検証のメリットは感じにくいと思います。
ですが、実際のウェブアプリを全世界に対して公開したような際には上記のような攻撃を防いでくれることができ、CSRF トークンや CSRF 検証は非常に重要な機能となっています。
特にウェブアプリでは、ユーザーから何らかの情報をフォームで送信してもらい、その情報を利用することで機能を実現するものが多いです。この際に、不正な送信フォームからリクエストが送信される可能性を考慮し、それに対して対策を行う必要があります。Django においては、その対策の1つが、CSRF トークンの発行や CSRF 検証となります。
Django における CSRF 対策 で解説したように、Django では CSRF 検証はデフォルトで有効化されており、フォーム送信によるリクエストを受け取った際に自動的に実行されるようになっています。
そのため、Django を利用してウェブアプリ開発するだけで CSRF 対策が行われるようになっていることになり、CSRF による攻撃を防ぐことができます。ただし、CSRF 検証が有効化されているために、CSRF トークンが含まれないフォームからのリクエストは拒否されることになっています。テンプレートファイルでフォームを作成する際に {% csrf_token %}
を記述しなければ、ウェブアプリ自身が生成したフォームからのリクエストも拒否されることになるので、その点は注意してください。
スポンサーリンク
まとめ
このページでは、Django における CSRF 対策について解説しました!
Django で開発されるウェブアプリは CSRF 検証を行うことで CSRF に対して対策が行われるようになっています。この CSRF 検証はデフォルト設定で有効化されており、フォームからのリクエストを受け取ったときに CSRF トークンの検証が行われるようになっています。
この CSRF 検証が行われるおかげで、不正なリクエストを見分け、CSRF による攻撃を防ぐことができます。
ただし、この CSRF 検証が行われるために、自身のウェブアプリでフォームを扱う際に、CSRF トークンの発行を行う必要があります。CSRF トークンの発行を忘れると、自身のウェブアプリのフォームからのリクエストも拒否され、下記のようなエラーが発生することになります。
CSRF検証に失敗したため、リクエストは中断されました。
CSRF verification failed. Request aborted.
この CSRF トークンの発行は、テンプレートファイルに {% csrf_token %}
を記述しておくことで実現することができます。CSRF トークンは送信フォームに埋め込んでおく必要があるので、<form>
から </form>
の間に記述しておく必要がある点に注意が必要です。
おまじないのように {% csrf_token %}
を記述している方もおられるかもしれませんが、このページで解説した通り、CSRF 検証はウェブアプリのセキュリティを高めるために重要で、送信フォームからのリクエストで CSRF 検証を成功させるためには {% csrf_token %}
を記述する必要があることは覚えておくと良いと思います!