SQL Injection: What is it?
Définition
SQL injection is an attack that exploits flaws in user input handling to execute arbitrary SQL commands on an application's database. Django's ORM natively protects against this vulnerability by automatically parameterising queries, but vigilance remains necessary with raw SQL queries.What is SQL Injection?
SQL injection (SQLi) is an attack technique that involves inserting malicious SQL code into an application's input fields to manipulate the underlying database. This vulnerability occurs when user-supplied data is integrated directly into an SQL query without proper validation or escaping. The attacker can then read, modify, or delete data, or even take complete control of the database server.
SQL injection has been ranked first in the OWASP Top 10 most critical web vulnerabilities for years. Despite the age of this threat and the existence of effective protections, it remains one of the most commonly discovered flaws during security audits. The reason is simple: any application that interacts with a relational database (PostgreSQL, MySQL, SQLite) is potentially exposed if queries are not properly parameterised.
In the Belgian and European context, a successful SQL injection can lead to a massive leak of personal data, constituting a GDPR violation with serious legal and financial consequences. Databases often contain a company's most sensitive assets: customer information, financial data, and intellectual property.
Why SQL Injection matters
SQL injection represents one of the most serious threats to web applications and information systems. Its potential consequences justify particular attention.
- Unauthorised data access: an attacker can extract the entirety of database tables, including credentials, hashed passwords, personal information, and financial data.
- Data modification and deletion: injected INSERT, UPDATE, and DELETE statements can corrupt or destroy critical business data, with no possibility of recovery if backups are not in place.
- Authentication bypass: a classic injection like
' OR 1=1 --in a login field can bypass password verification and grant access to any user account. - Privilege escalation: from the database, an attacker can sometimes execute system commands (via xp_cmdshell on SQL Server or COPY on PostgreSQL), compromising the entire server.
- Native protection by Django ORM: Django's ORM (Object-Relational Mapping) automatically parameterises all queries, strictly separating SQL code from user data, which eliminates the injection risk in standard queries.
How it works
The mechanism of SQL injection relies on the confusion between SQL code and user data. Consider a vulnerable query built by string concatenation: query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'". If an attacker enters admin' -- as the username, the query becomes SELECT * FROM users WHERE username = 'admin' --' AND password = ''. The double dash comments out the rest of the query, nullifying the password check.
More advanced injections exploit UNION operators to combine results from additional queries, subqueries to extract data from normally inaccessible tables, and blind injection techniques (blind SQLi) that deduce information from the application's behaviour (response times, error messages) without directly seeing the results.
The fundamental protection is the use of parameterised queries (prepared statements). Instead of concatenating data into the SQL query, positional markers are used that the database engine fills securely: cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", [username, password]). Django's ORM applies this principle automatically: User.objects.filter(username=username) generates a parameterised query with no possibility of injection. The danger only persists when developers use raw(), extra(), or cursor.execute() with non-parameterised data.
Concrete example
At Kern-IT, we develop business platforms with Django and PostgreSQL for clients handling sensitive data. In a healthcare (healthtech) project, the database contains patient information protected by GDPR and medical confidentiality. All data access goes through the Django ORM, guaranteeing automatic parameterisation of every query.
For cases requiring complex geospatial queries with PostGIS (within the KERN MAP framework), we use GeoDjango's spatial functions, which also generate parameterised queries. When a raw SQL query is unavoidable for performance reasons, we systematically apply parameterisation via cursor.execute(sql, params) and never string concatenation. Code reviews include a specific check of any use of raw() or extra() to ensure parameters are correctly handled.
Implementation
- Use the ORM systematically: favour Django ORM methods (filter, exclude, annotate, aggregate) for all database interactions. They automatically generate parameterised queries.
- Parameterise raw queries: if you must use
raw()orcursor.execute(), always pass user data via the params argument, never through concatenation or string formatting. - Validate inputs: apply strict server-side validation on all user data (type, length, format, allowed values) before using it in a query, even a parameterised one.
- Apply the principle of least privilege: configure the application's database user with minimal necessary permissions. No DROP, GRANT, or system command execution rights.
- Enable query logging: in development, enable Django's SQL logging to detect non-parameterised queries. In production, monitor abnormal queries via PostgreSQL logs.
- Scan with sqlmap: use sqlmap or OWASP ZAP in a test environment to verify that no entry point is vulnerable to SQL injection.
Associated technologies and tools
- Django ORM: database abstraction layer that automatically parameterises SQL queries, providing native injection protection.
- PostgreSQL: relational DBMS supporting prepared statements and offering granular access control (Row Level Security).
- sqlmap: open-source tool for automated detection and exploitation of SQL injections, used in penetration testing.
- OWASP ZAP: web security scanner that detects SQL injection points in forms and URL parameters.
- django-debug-toolbar: development tool that displays executed SQL queries, allowing parameterisation verification.
- WAF (Web Application Firewall): application firewall that filters requests containing SQL injection patterns before they reach the application.
Conclusion
SQL injection remains one of the most devastating vulnerabilities for web applications, but modern frameworks like Django offer effective native protection through their ORM. The key is discipline: use the ORM systematically, meticulously parameterise unavoidable raw queries, and rigorously validate all user inputs. At Kern-IT, we apply these principles across all our Django applications, from our business platforms to KERN MAP, ensuring the security of our Belgian clients' data in accordance with OWASP standards and GDPR requirements.
Enable LOGGING for SQL queries in development with 'django.db.backends' at DEBUG level. This lets you see exactly the queries generated by the ORM and immediately spot any query built by concatenation that might have slipped through code review.