This article describes the Django vulnerability CVE-2020-7471, which was fixed on February 3, 2020.
Please refer to the following for release information on the official website.
Django security releases issued: 3.0.3, 2.2.10, and 1.11.28 | Weblog | Django
Affected Versions
The following versions are affected.
- Django master branch
- Django 3.0
- Django 2.2
- Django 1.11
Vulnerability description[Potential SQL injection via StringAgg(delimiter)]
The class django.contrib.postgres.aggregates.StringAgg
for using the PostgreSQL STRING_AGG
function had a SQL injection vulnerability. It is possible to embed an arbitrary query in the value passed to the delimiter
parameter at initialization.
Proof of Concept
The libraries, Python and database versions used in the sample code are as follows.
- Libraries
- Django 3.0.2
- psycopg2-binary 2.8.4
- Python 3.8.1
- Database
- PostgreSQL 9.6.16
Create a database on PostgreSQL in advance, and change the DATABASES of the Django project to connect to PostgreSQL. (The following is a modification example)
|
|
Add an application called example
to the Django project and write the following code.
example/models.py
|
|
Execute ./manage.py makemigrations && ./manage.py migrate
to create the database tables to be used this time.
Make test data on the shell
command.
|
|
Next, let’s use StringAgg
on the shell
command. First, check for correct behavior.
|
|
The string foo;bar;test
is created between the ;
specified in delimiter
.
However, if you set delimiter="'"
, it will be like this.
|
|
Let’s take a look at the overall query that is actually generated.
|
|
Since the character passed to delimiter
is not escaped, SQL with syntax error is generated. If you devise it, you can insert another query into delimiter
.
Which Code Was Wrong and How It Was Fixed
See the GitHub code below for the actual changes.
In django.contrib.postgres.aggregates.StringAgg .__init__
, the code was to insert delimiter
directly into the expression using Python string embedding.
Therefore, the code was wrapped with django.db.models.Value
once and the value embedding was left to the database.
Since escape processing is not performed in Django, there is no omission of this change and another pattern of SQL injection.