English edition

2020年9月1日に修正されたDjangoの脆弱性CVE-2020-24583・CVE-2020-24584について解説します。

今回の脆弱性はアプリケーションが作成するディレクトリに余分なパーミッションを与えてしまう場合があります

今回の脆弱性はアプリケーションが作成するディレクトリに余分なパーミッションを与えてしまう場合があります

公式サイトでのリリース情報は以下を参照してください。

Django security releases issued: 3.1.1, 3.0.10 and 2.2.16 | Weblog | Django

影響を受けるバージョン

以下のバージョンが影響を受けます。

  • Django master branch
  • Django 3.1
  • Django 3.0
  • Django 2.2

脆弱性の内容

CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+

Python 3.7以上を使っている、かつFILE_UPLOAD_DIRECTORY_PERMISSIONSを使っている場合、collectstaticコマンド実行時に以下のディレクトリにFILE_UPLOAD_DIRECTORY_PERMISSIONSが適用されていませんでした。

  • ファイルをアップロードする過程で作成された中間レベルのディレクトリ
  • 中間レベルの収集された静的ディレクトリ

なお、Djnagoのバージョンアップを行っても既に作成されたディレクトリのパーミッションは変更されません。手動でパーミッションを変更する必要があります。

CVE-2020-24584: Permission escalation in intermediate-level directories of the file system cache on Python 3.7+

Python 3.7以上を使っている、かつファイルシステムキャッシュを使っている場合、キャッシュが置かれる中間レベルのディレクトリは、システム標準のumaskの値の影響を受けるようになっていました。本来は、umaskには0o077が適用されるべきです。

(つまり、「所有グループ」「その他」へのパーミッションは常に0になるべき)

脆弱性を利用した攻撃の例

サンプルコードで使っている環境のバージョンは以下の通りです。

  • Django 3.1
  • Python 3.8.5
  • Ubuntu 18.04.5

CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+

settings.pyに以下の設定を追加します。

1
2
STATIC_ROOT = BASE_DIR / "static"
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o700

collectstaticの実行後に作られるstaticディレクトリのパーミッションを調べてみます。

1
2
$ stat --format='%a' static
777

FILE_UPLOAD_DIRECTORY_PERMISSIONSの設定どおりなら0o700のはずですが、0o777になっています。

CVE-2020-24584: Permission escalation in intermediate-level directories of the file system cache on Python 3.7+

settings.pyに以下の設定を追加します。

1
2
3
4
5
6
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
        "LOCATION": BASE_DIR / "foo" / "bar",
    }
}

umask 022を実行してからshellコマンドで以下のコードを実行します。

1
2
>>> from django.core.cache import cache
>>> cache.set('my_key', 'hello, world!')

作成されたディレクトリのパーミッションを調べます。

1
2
$ stat --format='%a' foo
755

fooディレクトリを一旦消して、umask 000を実行してからshellコマンドで以下コード(前述のコードと同じ)を実行します。

1
2
>>> from django.core.cache import cache
>>> cache.set('my_key', 'hello, world!')

作成されたディレクトリのパーミッションを調べます。

1
2
$ stat --format='%a' foo
777

作成されたディレクトリのパーミッションがumaskの影響を受けていることが分かります。

どのコードに問題があったか・どのように修正されたか

実際の変更内容は以下のGitHubコードを参照してください。

CVE-2020-24583:

CVE-2020-24584:

CVE-2020-24583・CVE-2020-24584どちらもPython 3.7以降でのos.makedirsの仕様変更が原因です。

公式ドキュメントでのos.makedirsに関する説明を以下に引用します。

Changed in version 3.7: The mode argument no longer affects the file permission bits of newly-created intermediate-level directories.

引用元: os — Miscellaneous operating system interfaces — Python 3.8.6 documentation

検証のために、以下のコードを用意しました(Python 3.7以上が必要)。

1
2
3
4
5
import os
from pathlib import Path


os.makedirs(Path.cwd() / "foo" / "bar", mode=0o700)

一見、常にパーミッション0o700でディレクトリが作られるように見えますが、fooの「所有グループ」「その他」のパーミッションはumaskの値に依存します。

上記コードをumask 022の後実行するとfooのパーミッションは0o755、umast 000なら0o777になります。

これを回避するために、Djangoではos.makedirsmodeを指定しているコードでは前後にos.umaskを使うようになりました。

以下は上記のコードの修正例です。

1
2
3
4
5
6
7
8
9
import os
from pathlib import Path


old_umask = os.umask(0o077)
try:
    os.makedirs(Path.cwd() / "foo" / "bar", mode=0o700)
finally:
    os.umask(old_umask)