On Django Vulnerabilities CVE-2020-24583 and CVE-2020-24584 commentary


Japanese edition

This article describes the Django vulnerabilities CVE-2020-24583 and CVE-2020-24584, which were fixed on September 1, 2020.

This vulnerability can give extra permissions to directories created by the application

This vulnerability can give extra permissions to directories created by the application

See below for release information on the official website:

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

Affected Versions

The following versions are affected.

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

Vulnerability Description(Potential Account Hijack via Password Reset Form)

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

If you are using Python 3.7 or higher, and using FILE_UPLOAD_DIRECTORY_PERMISSIONS, The FILE_UPLOAD_DIRECTORY_PERMISSIONS was not applied to the following directories when executing the collectstatic command:

  • intermediate-level directories created in the process of uploading files
  • intermediate-level collected static directories

Please note that upgrading Django will not change the permissions of the directory already created. You will have to change the permissions manually.

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

If you are using Python 3.7 or higher and you are using filesystem caching, the mid-level directory where the cache is placed was affected by the system standard umask value. Originally, 0o077 should be applied to the umask.

(i.e., permissions to “owned group” and “other” should always be zero.)

Proof of Concept

The version of the environment used in the sample code is as follows:

  • Django 3.1
  • Python 3.8.5
  • Ubuntu 18.04.5

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

Add the following settings to settings.py:

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

Check the permissions of the static directory after the execution of collectstatic.

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

If it is set to FILE_UPLOAD_DIRECTORY_PERMISSIONS, it should be 0o700, but it is 0o777.

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

Add the following settings to settings.py:

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

Execute umask 022 and then execute the following code with the shell command:

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

Check the permissions of the created directory:

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

After deleting the foo directory, run umask 000 and execute the following code (same as above) with the shell command:

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

Check the permissions of the created directory:

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

You can see that the permissions of the created directories are affected by umask:

Which Code Was Wrong and How It Was Fixed

The actual changes can be found in the GitHub code below:

CVE-2020-24583:

CVE-2020-24584:

CVE-2020-24583 and CVE-2020-24584 are both due to a change in the os.makedirs specification in Python 3.7 and above.

The following is the description of os.makedirs from the official documentation:

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

References: os — Miscellaneous operating system interfaces — Python 3.8.6 documentation

For validation, we have prepared the following code (requires Python 3.7 and above):

1
2
3
4
5
import os
from pathlib import Path


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

At first glance, it looks like the directory will always be created with permission 0o700, but the permissions on foo, “ownership group” and “others” depend on the value of umask.

Running the above code after umask 022 will result in foo having 0o755 permissions, while umast 000 will result in 0o777.

To work around this, Django now uses os.umask before and after os.makedirs with mode in the code.

Here’s a modified example of the above code:

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)

comments powered by Disqus