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 — A basic analysis about CVE-2021-35942. SQL injection in Django. | Kitploit
Tools/GitHubGitHub/zer0qs/cve-2021-35042
Vulnerability AnalysisExploitationWeb SecurityPapers & ResearchLearning & Education
GitHubzer0qs/cve-2021-35042

CVE-2021-35042

A basic analysis about CVE-2021-35942. SQL injection in Django.

View Repository
24 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 on the MVC (Model - View - Controller) model. It was originally built to manage news content websites owned by the Lawrence publishing corporation, as a CMS (Content Management System) software.

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

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

Download Tool
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
Require AuthenticationNo required

II. Overview x2

0x01. Django's Model

In Django, creating tables and defining 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

The ORM framework built into Django is used to interact with the database, and the result of a query is a collection, which is a QuerySet.

order_by(fields) By default, order_by() returns a QuerySet sorted in the 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 in ascending order by id. The minus sign before the name field 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, it sorts 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 normally

cve202135042_wolf is the table name

First, the application directly calls the order_by() function; the code handling 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 to 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 as follows: wolves = Wolf.objects.order_by( 'name' , 'id' ) At that point, 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 checks each element in the array; if it is a string, it is 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 continue.
  2. if item == '?': If the element's value is the '?' character, the output results will be sorted randomly, 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 additional extras and if so, continue.

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

III. Analysis of the SQL injection vulnerability in Django

0x31. Cause

Django's ORM filters 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.

The 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 above code, we can see that if the parameter matches ? or starts with - followed by regular characters or ., the query is only then executed.

Therefore, when the column name is a UUID, it would be an invalid value and could not be passed to order_by. The change to this handling code was accepted and modified 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 in the item, it treats it as a query with a table name, and the continue command 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. Patch

In the current Django 4.0 version, querying by table name using . has been removed and is no longer supported; the patch was released for versions 3.1 and 3.2. Versions 3.2 -> 3.2.4 and 3.1 -> 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 restored.

0x33. Remediation

Update Django to a non-affected version.

IV. Demo

0x41. Environment

Docker & Docker-compose

0x42. Setup

  1. git clone https://github.com/WynSon/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. Access 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

Screen after installation is complete

0x43. Exploitation

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

When injecting the statement, we must know the table name to be able to execute the SQLi statement.

When entering an incorrect table name

When entering the correct table name, the orderby 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