This article describes the Django vulnerability CVE-2020-7471, which was fixed on February 3, 2020.

This vulnerability allows SQL injection
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.