nikkie-ftnextの日記

イベントレポートや読書メモを発信

DjangoにおけるXSS対策理解の一歩目。JavaScriptのコード片から作ったレスポンスを返してみる(HttpResponse・TemplateResponse)

はじめに

上達の早さは人それぞれだから、nikkieです。

Webアプリケーションの脆弱性の1つ、XSS(クロスサイト・スクリプティング)。
悪意を持ったHTMLやJavaScriptコードを注入できてしまう脆弱性です。
今回はDjangoにおけるXSS対策を理解するために、はじめの一歩を踏み出した記録です。

目次

HttpResponseによるレスポンスとXSS

Djangoのビュー関数から返すと、その内容をWebブラウザに表示できるHttpResponse1
ビュー関数でreturn HttpResponse("Hello world")と返すと、ブラウザには「Hello world」と表示されます。

HttpResponseを扱う際、XSSに注意です。
たとえばブラウザに「<script>alert("Hello world")</script>」と表示したいときに、return HttpResponse('<script>alert("Hello world")</script>')してはいけません

これは『実践Django』4.4でも取り上げられているまずい例です。
return HttpResponse('<script>alert("Hello world")</script>')としてしまうと、scriptタグがブラウザで実行されます

Webアプリから返されるページのソースをブラウザの機能で見ると

<script>alert("Hello world")</script>

scriptタグ自体です。
これはWebブラウザが実行してしまいますね。 alert("Hello world")自体は無害ですが、ここにユーザの入力した文字列が来てしまう場合を考えると(悪意のあるコードが実行されるわけなので)ゾッとします😨

エスケープしよう!

HttpResponseにHTMLやJavaScriptコードが渡る可能性がある場合、XSSの対策はエスケープです2
先ほどの例に適用すると

# example/views.py
from django.http import HttpResponse
from django.utils.html import escape


def example(request):
    return HttpResponse(escape('<script>alert("Hello world")</script>'))

scriptタグは実行されずに画面に表示されます!

ページのソースは

&lt;script&gt;alert(&quot;Hello world&quot;)&lt;/script&gt;

のようにエスケープされています!

TemplateResponse

Djangoのテンプレートを使う場合、JavaScriptのコードは実行されません。
Djangoのテンプレートでは{{ variable }}による変数展開でエスケープされるためです。

# example/views.py
from django.template.response import TemplateResponse


def example(request):
    context = {"message": '<script>alert("XSSです")</script>'}
    return TemplateResponse(request, "example/index.html", context)
<!-- example/templates/example/index.html -->
{{ message }}

ページのソースでは、エスケープされています!

&lt;script&gt;alert(&quot;Hello world&quot;)&lt;/script&gt;

Djangoのテンプレートとエスケープ

私はdjango-admin startprojectで生成したsettings.pyを使っているのですが、TEMPLATESという設定値があります。
生成したsettings.pyでは、django.template.backends.django.DjangoTemplatesが指定されています。

ドキュメントによると https://docs.djangoproject.com/ja/4.2/topics/templates/#django.template.backends.django.DjangoTemplates

'autoescape': a boolean that controls whether HTML autoescaping is enabled.
It defaults to True.

DjangoTemplatesのautoescape引数のデフォルト値がTrueです。
これによってエスケープされているわけですね!

出来心でsettings.pyの"OPTIONS""autoescape": Falseという組を追加したところ、XSSされました。

他にもエスケープを無効にするタグがあるようです。
ドキュメントの中の「自動エスケープをオフにする」より
https://docs.djangoproject.com/ja/4.2/ref/templates/language/#how-to-turn-it-off

サイト単位やテンプレート単位、あるいは変数単位でデータの自動エスケープ機能を無効にしたい場合には、いくつかの方法があります。

django.shortcutsのrenderを使う場合

https://github.com/django/django/blob/4.2.5/django/shortcuts.py#L17-L25
renderの返り値はHttpResponseですが、django.template.loader.render_to_stringの返り値から作られます。
これによりエスケープされた文字列からHttpResponseが作られているので、XSSは対策済みです。

% python manage.py shell
(InteractiveConsole)
>>> from django.template import loader
>>> context = {"message": '<script>alert("Hello world")</script>'}
>>> loader.render_to_string("example/index.html", context, None, using=None)
'&lt;script&gt;alert(&quot;Hello world&quot;)&lt;/script&gt;\n'

Djangoテンプレートの落とし穴

ここまでの内容からDjangoテンプレートを使っていれば常にXSSが対策されていると思われるかもしれませんが、それは結論を急ぎすぎみたいです。
ドキュメントの例がこちら3
https://docs.djangoproject.com/ja/4.2/topics/security/#cross-site-scripting-xss-protection

Django は HTML に対して特に危険とみなされる 特定の文字列のエスケープ を行います。この防御によってユーザーは大半の悪意のある入力から守られますが、いつも簡単で容易に利用できる訳ではありません。たとえば、次に示す例では防御がなされません。

<style class={{ var }}>...</style>

もし var の値が 'class1 onmouseover=javascript:func()' にセットされた場合、不完全な HTML をそのブラウザがどのようにレンダリングするかによっては、許可されていない Javascript を実行させる事になります。(このケースであれば属性値のクオートを行えば対処できます)

この例でのXSS対策は属性値をクォートで囲むことです4

<style class="{{ var }}">...</style>

属性値が囲まれていればonmouseoverという別の属性を注入されることはなくなりますね。
Djangoテンプレートのエスケープ機能でXSSを封じられます

終わりに

DjangoにおけるXSS対策の理解の第一歩として、レスポンスにJavaScriptコードを注入できるか手を動かしてみました。

  • HttpResponseJavaScriptコードを注入できる
    • ユーザの入力から作るような場合はescapeする!!
  • TemplateResponseではJavaScriptコードを注入できない(エスケープ済み)
    • django-admin startprojectしたとき、Djangoテンプレートはオートエスケープが有効に設定されている

Django Girls TutorialでDjangoを書き始めたのですが、テンプレートやrender関数をまず知ることで、XSSの落とし穴を回避するように巧妙にガイドされていたわけですね。
Webアプリ開発にはXSSという大きな落とし穴がいくつも空いているという印象ですが、その中を縫うようにDjangoは初学者でも一定安全なWebアプリ開発を提供していたのです、すごい!

今回手を動かしたアプリ(XSS脆弱性あり)はこちらです



  1. 『実践Django』3.1や3.4、akiyokoさんによる『現場で使えるDjangoの教科書《基礎編》』で取り上げられています
  2. 『実践Django』4.4、徳丸本(『安全なWebアプリケーションの作り方』)第2版 4.3.1
  3. akiyokoさんのDjangoCongress JP 2019トークで知りました。
  4. 徳丸本「属性値については、ダブルクォートで囲って、「<」と「"」と「&」エスケープする」(第2版 Kindle版 p.236)