On Django Vulnerabilities CVE-2020-13254 and CVE-2020-13596 commentary


Japanese edition

This article describes the Django vulnerabilities CVE-2020-13254 and CVE-2020-13596, which were fixed on June 3, 2020.

CVE-2020-13254 is a potential data breach

CVE-2020-13254 is a potential data breach

CVE-2020-13596 is a vulnerability related to XSS

CVE-2020-13596 is a vulnerability related to XSS

See below for release information on the official website:

Django security releases issued: 3.0.7 and 2.2.13 | 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-13254: Potential data leakage via malformed memcached keys

If you are using memcached, you can cause a data breach by passing an invalid key.

CVE-2020-13596: Possible XSS via admin ForeignKeyRawIdWidget

There is an XSS vulnerability in the query parameters generated by ForeignKeyRawIdWidget on admin.

This vulnerability can be exploited if the model has a ForeignKey.limit_choices_to or The ManyToManyField.limit_choices_to is specified. field.

Proof of Concept

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

  • Django 3.0.6
  • pylibmc 1.6.1
  • Python 3.8.3
  • memcached 1.6.6
  • libmemcached 1.0.18

CVE-2020-13254: Potential data leakage via malformed memcached keys

First, edit settings.py as described in the official documentation. Here is an example configuration:

1
2
3
4
5
6
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',  # Change it accordingly depending on where you put memcached.
    }
}

Then, run the following on the shell command:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
>>> from django.core.cache import cache
>>> cache.set('my_key', 'hello, world!')
>>> cache.get('my_key')
'hello, world!'
>>> c = chr(33)
>>> c
'!'
>>> cache.get('my_key' + c)
>>> c = chr(32)
>>> c
' '
>>> cache.get('my_key' + c)  # should return None
'hello, world!'

The last cache.get('my_key' + c) should return None. can get the value set by the 'my_key' key and return the value of `hello, world! I’m done.

The last cache.get('my_key' + c) should return None. But I was able to get the value hello, world! set by the 'my_key' key.

CVE-2020-13596: Possible XSS via admin ForeignKeyRawIdWidget

You will need the following code:

example/models.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from django.db import models


class Band(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class UnsafeLimitChoicesTo(models.Model):
    band = models.ForeignKey(
        Band,
        models.CASCADE,
        limit_choices_to={'name': '"&><escapeme'},
    )

example/admin.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from django.contrib import admin

from . import models


admin.site.register(models.Band)


class UnsafeLimitChoicesToAdmin(admin.ModelAdmin):
    raw_id_fields = (
        'band',
    )


admin.site.register(models.UnsafeLimitChoicesTo, UnsafeLimitChoicesToAdmin)

Create a database by makemigrations and migrate command, and create an admin user by createsuperuser command.

The shell command creates the following data:

1
2
3
>>> from example.models import Band
>>> Band.objects.create(name='"&><escapeme')
<Band: "&><escapeme>

Run the runserver command to start up the development server and create UnsafeLimitChoicesTo from the following URL. Set Band to 1:

http://127.0.0.1:8000/admin/example/unsafelimitchoicesto/add/

View the data after registration and check the HTML of the following magnifying glass button (in the red box).

Displayed content of http://127.0.0.1:8000/admin/example/unsafelimitchoicesto/1/change/

Displayed content of http://127.0.0.1:8000/admin/example/unsafelimitchoicesto/1/change/

The "&><escapeme part is embedded without escaping, as shown below:

1
<a href="/admin/example/band/?name="&><escapeme&amp;_to_field=id" class="related-lookup" id="lookup_id_band" title="Lookup"></a>&nbsp;<strong><a href="/admin/example/band/1/change/">&quot;&amp;&gt;&lt;escapeme</a></strong>

If you write a value like '"></a><script>alert(\'XSS\');</script><a href="' in limit_choices_to, you can put a script in admin.

Which Code Was Wrong and How It Was Fixed

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

CVE-2020-13254:

CVE-2020-13596:

CVE-2020-13254: Potential data leakage via malformed memcached keys

django.core.cache.backends.memcached.BaseMemcachedCache passes the input value directly to memcached, and the It was.

So, we add the validate_key method to check the input value, and if an invalid value is found, the the key is not passed to memcached, but instead a warning message is output if the It was.

In Django 3.0.7, running the code listed in the “Proof of Concept” section above, we found The following message will be displayed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
>>> from django.core.cache import cache
>>> cache.set('my_key', 'hello, world!')
>>> cache.get('my_key')
'hello, world!'
>>> c = chr(33)
>>> c
'!'
>>> cache.get('my_key' + c)
>>> c = chr(32)
>>> c
' '
>>> cache.get('my_key' + c)
****/.venv/lib/python3.7/site-packages/django/core/cache/backends/base.py:253: CacheKeyWarning: Cache key contains characters that will cause errors if used with memcached: ':1:my_key '

CVE-2020-13596: Possible XSS via admin ForeignKeyRawIdWidget

In django.contrib.admin.widgets.ForeignKeyRawIdWidget.get_context, This was due to passing the related_url variable (used to embed the value of limit_choices_to into the magnifying glass button) using the django.utils.safestring.mark_safe function. The value using django.utils.safestring.mark_safe is It will not be escaped on the template.

The modified code now uses django.utils.http.urlencode instead of django.utils.safestring.mark_safe.

In Django 3.0.7, running the code listed in the “Proof of Concept” section above, you can find that The "&><escapeme will now be escaped:

1
<a href="/admin/example/band/?name=%22%26%3E%3Cescapeme&amp;_to_field=id" class="related-lookup" id="lookup_id_band" title="Lookup"></a>&nbsp;<strong><a href="/admin/example/band/1/change/">&quot;&amp;&gt;&lt;escapeme</a></strong>

comments powered by Disqus