SQL Injection Prevention with the ORM
In this page:
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.
# 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.
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.
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. #}
- Building raw SQL strings by concatenating user input directly into the query text.
- Using .raw() or .extra() with unescaped user input instead of parameterized values.
- Assuming SQL injection can't happen in Django because 'the ORM handles it' even when raw SQL is used elsewhere in the project.
- 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.
Chapter Quiz — Complete all 9 topics to unlock
0/9 topics done
Complete these topics first: