How To Avoid SQL Injection on Django ORM Framework
Read Time 8 mins | 05 July 2025

Introduction
With the advent of newer technologies and frameworks, Django took a really important place in the industry of web development frameworks. Django is a well-known and heavily tested open source Python framework which became public in 2005 with its first version. The framework follows a Model-Template-View (MTV) architectural pattern and, alongside with the variety of tools offered, it includes a stable Object Relational Mapping (ORM) framework.
History of ORMs
In computer science, object-relational mappings is a programming technique that converts data from a relational database into objects that can be used programmatically during the development of an application. The primary objective of the methodology is to offer an abstract layer between the relational database and the application in a seamless way, without the necessity for the developer to actually write SQL queries.
For reference, a quick comparison between raw SQL queries and ORM-based queries.The code snippet below illustrates the traditional way to extract DBMS data into a scalar value that will be used in the workflow.
var sql = "SELECT id, first_name, last_name, phone, birth_date, sex, age FROM persons WHERE id = 10";
var result = context.Persons.FromSqlRaw(sql).ToList();
var name = result[0]["first_name"];
The following, on the other hand, is an example of the same query with an ORM layer.
var person = repository.GetPerson(10);
var firstName = person.GetFirstName();
How ORMs Work Under the Hood
While ORMs could technically understand how data has been represented in the DBMS by inspecting the database schema and generate an object-like structure automatically, it is considered standard practice for frameworks like Django to define a specific declarative model file which will then be used as a reference to define:
- Entities: which are bound to the specific tables of the DMBS and will be represented as objects
- Relationships: or how one entity is tied to another and which type of association is expected (OneToOne, OneToMany, ManyToMany, ManyToOne)
- Persistence: or how the framework guarantees that each transaction, either CREATE, UPDATE, DELETE, is going to be executed even when the application is closed or restarted. Some frameworks also rely on caching and disk-based session persistence methodologies to achieve the same result.
A simple model data structure could be represented as follows:
from datetime import date
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField(default=date.today)
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField(default=0)
number_of_pingbacks = models.IntegerField(default=0)
rating = models.IntegerField(default=5)
def __str__(self):
return self.headline
The above example, when the migration is applied via the CLI tool, will be translated into the following data structure:
So during the execution of our application, the data can be queried:
# Creating a new blog
from blog.models import Blog
b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
b.save()
# Querying a blog
from blog.models import Blog
b = Blog.objects.get(pk=1)
print(b.name) # Will output "Beatles blog"
# Updating a blog
from blog.models import Blog
b = Blog.objects.get(pk=1)
b.name = "Guns n roses blog"
b.save() # Blog title will be "Guns n roses"
Securing Django Applications from SQL Injection Attacks
SQL Injection (SQLi) is a type of attack where an attacker inserts malicious SQL code into a query, allowing them to manipulate databases and access sensitive information. The attack arises whenever a malicious actor is able to convert input supplied data like query strings, post parameters or cookie values into a SQL query. Consequences of this attack could vary from identity spoofing, data stealing/disclosure/loss, lateral privilege escalations or, in some cases, command execution.
The examples from the previous section showed how it is possible to interact with the Django framework and extract data from the DBMS, but is it possible to achieve a SQL injection in the following snippet?
from blog.models import Blog
input_name = request.POST.get("blog_name") # Takes the blog name from POST data
b = Blog.objects.filter(blog_name == input_name)
print(b.name)
Fortunately the above example is not vulnerable to SQL injection attacks, since everything that goes into the .filter()
method will be parameterised automatically. But what happens whenever there is the necessity to build a custom and complex query?
from blog.models import Blog
input_name = request.POST.get("blog_name") # Takes the blog name from POST data
# vulnerable code, do not use
b = Entry.objects.raw("SELECT * FROM entries LEFT JOIN blog ON blog.id = entry.blog_id WHERE blog.name = '"+input_name+"'")
print(b.name)
As specified in the official Django documentation, no checking is done on the SQL statement passed on the raw function, making it possible to achieve a SQL injection in the traditional way by appending an OR condition to the classic query string OR 1=1.https://docs.djangoproject.com/en/5.1/topics/db/sql/#performing-raw-queries
Writing Secure Custom SQL queries Without Relying on the Django Framework APIs
In some circumstances, especially when the query is complex enough and using the declarative APIs of the Django ORM could be cumbersome, it is possible to achieve the same result by interacting with the Python Database API directly like the following:
from django.db import connection
cursor = connection.cursor()
input_name = request.POST.get("blog_name") # Takes the blog name from POST data
cursor.execute('SELECT * FROM entries LEFT JOIN blog ON blog.id = entry.blog_id WHERE blog.name = "%s"', (input_name,))
cursor.close()
In this case the WHERE condition parameter will be escaped by the library without further intervention and it could be considered as a best practice in order to avoid injection issues whenever there is the necessity to interact with the Python Database API directly.
Note: by using the Python Database API interface, the result set will be represented as a scalar, so it will not be mapped to the existing Entities defined in the model file. Additional code should be implemented in order to translate the scalar values into an entity.
Conclusion
While ORMs like the one included in the Django Framework can be particularly useful to avoid SQL injection issues, some of the APIs offered by the framework should be analysed carefully before the implementation.
The Python Database API, if used correctly, can be considered as a valid alternative when dealing with complex data structures or queries.
Additional Django Security Best Practices
In general there are some security countermeasures to take in consideration when dealing with a Django ORM based application.
- Since the Python language is not statically typed, all the input parameters, including the ones declared as integers, will be available in the request object as strings. Additional casting logic should be added to the code base if expecting one specific type of data.
- The
`.raw()`
function of the Django ORM should be used with care, and only when the function does not expect input data that will be concatenated into a query string. - Use the Django ORM API whenever possible.
Footnotes
- The security countermeasures described above do not protect against any type of client-side injection attacks (Cross-Site Scripting (XSS), Server-Side Template Injection (SSTI) and related). Further validation and/or filtering mechanisms should be implemented to prevent the above mentioned.
- Another issue to take in consideration is related to Insecure Direct Object Reference (IDOR) attacks. While the application filters any possibility to inject custom SQL statements, it is possible for an attacker to access resources directly by providing arbitrary IDs, either numeric or string-based. A comprehensive and thoughtful access control list mechanism, especially when dealing with multi-tenancy systems, is recommended.
Join Our Newsletter