2019年12月18日に修正されたDjangoの脆弱性CVE-2019-19844について解説します。
公式サイトでのリリース情報は以下を参照してください。
Django security releases issued: 3.0.1, 2.2.9, and 1.11.27 | Weblog | Django
影響を受けるバージョン
以下のバージョンが影響を受けます。
- Django master branch
- Django 3.0
- Django 2.2
- Django 1.11
脆弱性の内容(Potential account hijack via password reset form)
パスワードリセット機能を悪用して、攻撃者が既存ユーザーのパスワードを任意の値に変更することができます。
脆弱性を利用した攻撃の例
今回はコード量が多いので、以下GitHubリポジトリを用意しました。
ryu22e/django_cve_2019_19844_poc
上記アプリケーションを動かすには、以下の環境が必要です。
- PostgreSQL 9.5以上
- Python 3.7.x
また、以下の前提で作られています。
- 既存ユーザー
- ユーザー名
mike123
、メールアドレスmike@example.org
- ユーザー名
- 攻撃者のメールアドレス
mıke@example.org
1
READMEの「Setup」に従って既存ユーザーを作るところまで進めてください。
次に、READMEの「Procedure For Reproducing」に従ってパスワードリセットを実行してください。
攻撃者が用意したメールアドレスmıke@example.org
にmike123
ユーザーのパスワードリセット用メールが送られ、そこからmike123
ユーザーのパスワードを勝手に変更することができたはずです。
どのコードに問題があったか・どのように修正されたか
実際の変更内容は以下のGitHubコードを参照してください。
まず、修正前のコードを確認してみましょう。django.contrib.auth.forms.PasswordResetForm.save
メソッドからパスワードリセット用メールが送られる流れは以下のとおりです。
- フォームから入力されたメールアドレスがデータベースに存在するか確認
- (
1.
でデータが存在していたら)フォームから入力されたメールアドレスにメールを送信
1.
に該当するコードを以下に引用します。
|
|
一見問題がないように見えますが、iexact
で大文字・小文字を区別しないようにしている点に罠が潜んでいます。先述のryu22e/django_cve_2019_19844_pocの「Setup」までを済ませた状態で、shell
コマンド上で以下コードを実行してみると分かります。
|
|
本来、mike@example.org
とmıke@example.org
は違うメールアドレスですが、iexact
ではi
とı
が同一文字と判断されるために、該当データが見つかってしまいます。
さらに、2.
では「フォームから入力されたメールアドレス」にメールを送信するため、データベース上に存在しないメールアドレスmıke@example.org
にメールが送られます。
これらの問題に対して、2点の変更がありました。公式サイトのリリースノート「To resolve this, two changes were made in Django:」に書かれている説明を以下に引用します。
- After retrieving a list of potentially-matching accounts from the database, Django’s password reset functionality now also checks the email address for equivalence in Python, using the recommended identifier-comparison process from Unicode Technical Report 36, section 2.11.2(B)(2).
- When generating password-reset emails, Django now sends to the email address retrieved from the database, rather than the email address submitted in the password-reset request form.
順番に解説します。
- After retrieving a list of …
データベースから取得したメールアドレスとフォームから入力されたメールアドレスを比較して、値が一致しないものは無視するようになりました。
値の比較はUnicode Technical Report 36, section 2.11.2(B)(2).が推奨する方法に従って、unicodedata.normalizeでUnicode正規化し、str.casefoldで小文字化した文字列を対象にしています。
これで、大文字・小文字を区別せずメールアドレスを入力できる機能は残しつつ、iexact
による想定外の値を取り除くことができるようになりました。
- When generating password-reset emails, …
メール送信はフォームに入力されたメールアドレスではなく、データベースに登録されているものに対して行われるようになりました。これにより、もし1.
にバグがあっても攻撃者にメールが届くことはなくなりました。