Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2021-35042 — Django SQL injection vulnerability | Kitploit
Tools/GitHubGitHub/luuanhduc/cve-2021-35042
Vulnerability AnalysisExploitationWeb Application ExploitationLearning & EducationDatabase SecurityLabs & Practice
GitHubluuanhduc/cve-2021-35042

CVE-2021-35042

Django SQL injection vulnerability

View Repository
13 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2021-35042: Django SQL injection vulnerability

I. Overview

Django is an open-source Web Application Framework written in Python, built according to the MVC (Model - View - Controller) model. It was originally built to manage news content websites owned by the Lawrence publishing corporation, as CMS (Content Management System) software.

Django versions 3.1.x through 3.1.13 and versions 3.2.x through 3.2.5 contain an SQL injection vulnerability.

The cause of this vulnerability is that the input data filtering function for user-controlled data in QuerySet.order_by() is insufficient to prevent SQL injection attacks. This vulnerability can be exploited to allow attackers to perform unauthorized actions, leading to the leak of sensitive data.

CVE - IDCVE-2021-35042
Severity9.8 - CRITICAL
CWE - IDCWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Vulnerability Publication Date1/7/2021
Affected Software3.1.x < 3.1.13, 3.2.x < 3.2.5
Requires AuthenticationNot required

II. Overview x2

0x01. Django's Model

In Django, creating tables and defining the fields in the database is done by declaring a model class in the models.py file. In this example, we declare a table named Wolf and a field named name.

0x02. QuerySet and Order_by() in Django

Django's built-in ORM framework is used to interact with the database, and the result of a query is a set, which is a QuerySet.

order_by(fields) By default, order_by() returns a QuerySet sorted in an order specified in the ordering option in the Model's Meta. We can override the order_by condition in each query by using the order_by() method.

Example

wolves = Wolf.objects.order_by('-name', 'id')

The result of the above query will be sorted in descending order by the name field, then ascending by id. The minus sign before the field name name indicates that the results are sorted in descending order.

The following example sorts the returned results by the field received from the user; if no value is passed in, it will sort by the id field.

Result

In versions 3.1 and 3.2, Django allows combining query methods with table names in the order_by query. This is also the main cause of this vulnerability.

Passing a table name gives us the same result as passing a field name as usual

cve202135042_wolf is the table name

First, the application directly calls the order_by() function; the code that handles the order_by() function is defined at: django/db/models/query.py

The order_by() function does 2 things

  1. Clears all current methods being called by order_by() and removes the default parameter passed in when order_by receives a different value.
  1. Passes the parameter into order_by. The add_ordering() function performs this task
root@kitploit:~
def add_ordering(self, *ordering):
        """
        Add items from the 'ordering' sequence to the query's "order by"
        clause. These items are either field names (not column names) --
        possibly with a direction prefix ('-' or '?') -- or OrderBy
        expressions.

        If 'ordering' is empty, clear all ordering from the query.
        """
        errors = []
        for item in ordering:
            if isinstance(item, str):
                if '.' in item:
                    warnings.warn(
                        'Passing column raw column aliases to order_by() is '
                        'deprecated. Wrap %r in a RawSQL expression before '
                        'passing it to order_by().' % item,
                        category=RemovedInDjango40Warning,
                        stacklevel=3,
                    )
                    continue
                if item == '?':
                    continue
                if item.startswith('-'):
                    item = item[1:]
                if item in self.annotations:
                    continue
                if self.extra and item in self.extra:
                    continue
                # names_to_path() validates the lookup. A descriptive
                # FieldError will be raise if it's not.
                self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
            elif not hasattr(item, 'resolve_expression'):
                errors.append(item)
            if getattr(item, 'contains_aggregate', False):
                raise FieldError(
                    'Using an aggregate in order_by() without also including '
                    'it in annotate() is not allowed: %s' % item
                )
        if errors:
            raise FieldError('Invalid order_by arguments: %s' % errors)
        if ordering:
            self.order_by += ordering
        else:
            self.default_ordering = False
            

The parameter passed to add_ordering() is an array.

For example, when the parameter is passed in as follows: wolves = Wolf.objects.order_by( 'name' , 'id' ) Then, the application will convert it into the following database query:

root@kitploit:~
SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY "cve202135042_wolf"."name" ASC, "cve202135042_wolf"."id" ASC

When passed in, the add_ordering function will check each element in the array; if it is a string, it will be checked against the following 5 cases:

  1. if '.' in item: It checks whether it is a query with a column name and whether that column has a table name specified in the SQL statement. If so, it issues a warning and continues.
  2. if item == '?': If the element value is the '?' sign, the output results will be sorted randomly, then continue.
  3. if item.startswith('-'): If the item starts with the '-' character, the query results will be sorted DESC (Descending).
  4. if item in self.annotations: It checks whether it contains a comment; if so, continue.
  5. if self.extra and item in self.extra: Determines whether there are extras; if so, continue.

After the 5 checks, the parameter is then passed to the function self.names_to_path(item.split(LOOKUP_SEP), self.model._meta) to continue checking whether it is a valid column name; then, if valid, it is added to self.ordering of the Query class for further processing.

III. Analyzing the SQL injection vulnerability in Django

0x31. Cause

Django's ORM filters the data inserted into queries very strictly, but this source code change leading to SQL injection is because the author hypothesized that if the column name is a UUID (Universal Unique Identifier) column, the order_by query could not be executed.

That is, if the input data is xxx-xxx-xxx-xxx (UUID format), the query cannot be executed.

Code before the change

root@kitploit:~
# django/db/models/sql/constants.py 
ORDER_PATTERN  =  _lazy_re_compile ( r '\?|[-+]?[.\w]+$' )

# django/db/models/sql/query.py 
def  add_ordering ( self ,  * ordering ): 
        errors  =  [] 
        for  item  in  ordering : 
            if  isinstance ( item ,  str )  and  ORDER_PATTERN . match ( item ): 
                if  '.'  in  item : 
                    warnings . warn ( 
                        'Passing column raw column aliases to order_by() is ' 
                        'deprecated. Wrap %r in a RawSQL expression before '
                        'passing it to order_by().'  %  item , 
                        category = RemovedInDjango40Warning , 
                        stacklevel = 3 , 
                    ) 
            elif  not  hasattr ( item ,  'resolve_expression' ): 
                errors . append ( item ) 
            if  getattr ( item ,  'contains_aggregate' ,  False ): 
                raise  FieldError ( 
                    'Using an aggregate in order_by() without also including ' 
                    'it in annotate() is not allowed: %s ' %  item 
                ) 
        if  errors : 
            raise  FieldError ( 'Invalid order_by arguments: %s '  %  errors ) 
        if  ordering : 
            self . order_by  +=  ordering 
        else : 
            self . default_ordering  =  False

From the code above, we can see that if the parameter matches ? or starts with - followed by regular characters or a . sign, then the query is executed.

Therefore, when a column name is a UUID, it is an invalid value and cannot be passed to order_by.

The change to this handling code was accepted, and it was changed as follows: https://github.com/charettes/django/commit/513948735b799239f3ef8c89397592445e1a0cd5

It used the self.name_to_path function to validate the input data.

But after checking, if . is present in the item, it treats it as a query with a table name; the continue statement is executed, which directly skips using the self.name_to_path function to validate the data.

The code handling the . in the get_order_by function is as follows: django/db/models/sql/compiler.py

root@kitploit:~
if  '.'  in  field : 
    table ,  col  =  col . split ( '.' ,  1 ) 
    order_by . append (( 
            OrderBy ( 
                RawSQL ( ' %s . %s '  %  ( 
                self . quote_name_unless_alias ( table ),  col ),  [ ]), 
                descending = descending 
            ),  False )) 
    continue

The self.quote_name_unless_alias function handles the table name, filters valid table names, and skips filtering the column name, so we can inject an SQL injection statement.

0x32. The Patch

In the current Django 4.0 version, querying by table name using . has been removed and is no longer supported; patches were released for versions 3.1 and 3.2. Versions 3.2 through 3.2.4 and 3.1 through 3.1.12 are affected.

3.2.x Fixed CVE-2021-35042 -- Prevented SQL injection in QuerySet.o…

3.1.x Fixed CVE-2021-35042 -- Prevented SQL injection in QuerySet.o…

The fix is very simple: the old ReGex data validation was brought back.

0x33. Remediation

Update Django to a non-affected version.

IV. Demo

0x41. Environment

Docker & Docker-compose

0x42. Setup

  1. git clone https://github.com/LUUANHDUC/CVE-2021-35042.git
  2. Run ./setup.sh for initial setup
  3. sudo docker-compose up --build
  4. sudo docker exec -it cve-2021-35042_web_1 python manage.py makemigrations cve202135042
  5. sudo docker exec -it cve-2021-35042_web_1 python manage.py migrate
  6. Visit http://localhost:8000/load_example_data to load sample data:
  7. The path containing the vulnerable param: http://localhost:8000/wolves/ http://localhost:8000/wolves/?order_by=name

The screen after the installation is complete

0x43. Exploitation

Condition: To be able to exploit it, we must know the table name somehow :))

When injecting the statement, we must know the table name in order to execute the SQLi statement.

When entering an incorrect table name

When entering the correct table name, the order_by query executes normally.

The statement at this point will become SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY ("cve202135042_wolf"."name") ASC

At this point, we can terminate the preceding order_by statement and inject an SQL statement to exploit it.

SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY ("cve202135042_wolf"."name"); SELECT * from cve202135042_wolf where id =1; --) ASC

V. Reference

https://www.djangoproject.com/weblog/2021/jul/01/security-releases/ https://xz.aliyun.com/t/9834 https://www.bugxss.com/vulnerability-report/3095.html https://blankheart.top/2022/04/07/cve-2021-35042/ https://itcn.blog/p/1648921763575859.html https://github.com/YouGina/CVE-2021-35042

Download Tool