← Back to Django Course | Chapter 15: Security, Caching & Deployment | Lesson 5 of 9

SQL Injection Prevention with the ORM

The ORM is like a translator who always asks 'what exactly do you mean?' before talking to the database, so a sneaky visitor can't slip in a hidden command disguised as a search box.

The Unsafe Way: Raw String SQL

Concatenating user input directly into a SQL string lets an attacker inject their own SQL. If username came from a form as ' OR 1='1, the query's meaning changes completely. This pattern should never be used.

Warning: This example shows what NOT to do -- it is vulnerable to SQL injection and is only here for comparison.

Example: The Unsafe Way: Raw String SQL

Concatenating user input directly into a SQL string lets an attacker inject their own SQL. If username came from a form as ' OR 1='1, the query's meaning changes completely. This pattern should never be used.

markup
# UNSAFE -- do not do this:
# query = "SELECT * FROM auth_user WHERE username = '" + username + "'"
# cursor.execute(query)
#
# If username is: ' OR '1'='1
# the query becomes true for every row in the table.
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}

The Safe Way: The ORM

Django's QuerySet methods like .filter() never insert values directly into the SQL text. Instead, the value is passed as a separate parameter to the database driver, so it's always treated as plain data, never as part of the SQL command.

Note: The ORM is safe by default -- you get this protection just by using .filter() normally.

Example: The Safe Way: The ORM

Django's QuerySet methods like .filter() never insert values directly into the SQL text. Instead, the value is passed as a separate parameter to the database driver, so it's always treated as plain data, never as part of the SQL command.

markup
from django.contrib.auth.models import User

# Safe: username is passed as a parameter, not concatenated
username = request.POST.get('username')
matches = User.objects.filter(username=username)
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}

Parameterized Raw SQL, When You Need It

Occasionally raw SQL is unavoidable. Django's .raw() and cursor.execute() support %s placeholders with a separate parameters list, keeping the same safety guarantee as the ORM.

Example: Parameterized Raw SQL, When You Need It

Occasionally raw SQL is unavoidable. Django's .raw() and cursor.execute() support %s placeholders with a separate parameters list, keeping the same safety guarantee as the ORM.

markup
from django.db import connection

def find_user(username):
    with connection.cursor() as cursor:
        cursor.execute(
            'SELECT id, username FROM auth_user WHERE username = %s',
            [username],
        )
        return cursor.fetchone()
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}
Common Mistakes
  1. Building raw SQL strings by concatenating user input directly into the query text.
  2. Using .raw() or .extra() with unescaped user input instead of parameterized values.
  3. Assuming SQL injection can't happen in Django because 'the ORM handles it' even when raw SQL is used elsewhere in the project.
Chapter Summary
  • SQL injection happens when untrusted input is inserted directly into a SQL query string and executed as code.
  • Django's ORM builds parameterized queries automatically, so values are always sent separately from the query structure.
  • Using .filter(), .exclude(), and other ORM methods with user input is safe by default.
  • Raw SQL is only safe when parameters are passed separately, never string-formatted into the query.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.