nikkie-ftnextの日記

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

#djangocongress トーク延長戦:XSSでcookieを他サーバに送信するため、Djangoのセッションの設定を緩める(脆弱性の作り込みなので真似しないでください)

はじめに

DjangoCongress JP 2023、1日ありがとうございました! nikkieです。

トークした「Djangoアプリに作り込んで学ぶ脆弱性SQLインジェクションXSS篇)」について延長戦コンテンツをお届けします

目次

トークDjangoアプリに作り込んで学ぶ脆弱性SQLインジェクションXSS篇)」

https://djangocongress.jp/#talk-7

XSS(持続型XSS)の例として、cookieを他サーバに送信するデモをしました。
JavaScriptで以下を実装しています

  • document.cookiecookieにアクセス
  • window.locationにURLを代入して、他サーバにcookieを送信

質疑の中で「document.cookiecookieにアクセス」について、DjangoではデフォルトでJavaScriptからcookieにアクセスできない設定なので、追加の設定をする必要があるというご指摘と理解しました。

nikkieの勘違い:デモで見せたcookieDjangoアプリによるものではなかった

ここは完全に勘違いしていました。申し訳ありません。

今回の発表のアプリは、徳丸さんによるBad Todoのオマージュです。

Bad Todoを127.0.0.1で動かしていたのですが、その中で127.0.0.1cookieTODOSESSIDが設定されたと思われます1csrf_tokenも同様かも)。
つまり、デモで見せたcookieは、徳丸さんによるBad Todoのcookieだったと分かりました(Djangoアプリでなかった)。

Djangoのセッションのデフォルト設定

今回はstartprojectしたアプリですので、セッションが有効になっています。
ref: https://docs.djangoproject.com/ja/4.2/topics/http/sessions/#enabling-sessions

セッションエンジンとしては、デフォルト値の'django.contrib.sessions.backends.db'です。
ref: https://docs.djangoproject.com/ja/4.2/ref/settings/#session-engine

つまり、データベースを使ったセッションであって、cookieを使ったセッションではありませんでした

この設定は、cookieを送信するXSSの例としては不適切だったと分かりました。
発表内容に正確でない点があり、申し訳ありません。
(今回のデモの攻撃が成り立たないということで、セキュアではあります)

XSScookieを他サーバに送信するために緩めた設定(非推奨)

cookieを送信するXSSの例に沿った設定をします(=セキュアでない状態にします)。
プロジェクトのsettings.pyに以下を追加しました

SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
SESSION_COOKIE_HTTPONLY = False
SESSION_COOKIE_NAME = "TODOSESSID"

SESSION_ENGINEをデフォルト値からクッキーを使ったセッションに変更します。

「クッキーを使ったセッション」には

保存されたデータに JavaScript からアクセスできないように、SESSION_COOKIE_HTTPONLY を True に設定しておくことをおすすめします。

とあります。
SESSION_COOKIE_HTTPONLYのデフォルト値はTrueなので、デフォルトではcookieに保存されたデータにJavaScriptからアクセスできません2
https://docs.djangoproject.com/ja/4.2/ref/settings/#session-cookie-httponly
脆弱性を作り込んでデモをするという目的のために、Falseに緩めます。

SESSION_COOKIE_NAMEcookieのキーを徳丸さんのBad Todoと合わせるために指定しました(オマージュなので!)
https://docs.djangoproject.com/ja/4.2/ref/settings/#session-cookie-name

これでトークと同様にデモをし、TODOSESSIDを含むcookieがモックサーバに送られることを確認しています。
また、通常のブラウザとシークレットウィンドウとで別々のユーザでログインし、TODOSESSIDが変わることも確認しました。

終わりに

Djangoのセッションのcookieについて、トークをきっかけに自分の誤解に気づきました。
トークのデモにはsettings.pyの設定も必要」と追加して訂正いたします。

SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
SESSION_COOKIE_HTTPONLY = False
SESSION_COOKIE_NAME = "TODOSESSID"

トークする前に気付ければとてもよかったわけですが、発表へのコメントを機に甘かった理解を更新できたので、コメントしてくださった方(うめさん)には大感謝です。
また、本発表を聞きに来て(一緒に悪いことして)くださった皆さま、ありがとうございました!