flawopen.com/SQL Injection en Python/Django ORM
¿El ORM de Django protege frente a SQL Injection?
La API QuerySet de Django separa el SQL de los valores y los envía como parámetros al controlador de la base de datos en consultas habituales como .filter(), .get() y .exclude(). Esa protección no cubre todas las formas de construir consultas: el SQL sin procesar, las expresiones personalizadas y la estructura de consulta elegida por el usuario requieren una revisión propia.
Uso seguro de valores en QuerySets de Django
username = request.GET.get("u", "")
User.objects.raw(
f"SELECT * FROM myapp_user WHERE username = '{username}'"
)
username = request.GET.get("u", "")
User.objects.raw(
"SELECT * FROM myapp_user WHERE username = %s",
[username],
)
Dónde requiere cuidado la protección
Manager.raw() ejecuta el SQL que proporcionas. Pasa los valores controlados por el usuario mediante params y deja los marcadores sin comillas, por ejemplo User.objects.raw("SELECT * FROM users WHERE username = %s", [username]). No insertes valores en el SQL con f-strings, formato % ni concatenación. Los parámetros representan valores, no identificadores SQL como nombres de tablas o columnas.
Para acceder directamente a la base de datos, usa cursor.execute(sql, params) y pasa los valores por separado con los marcadores documentados por Django. No interpoles valores en sql. Revisa también RawSQL y las expresiones Func: el contexto de plantilla y los argumentos con nombre pueden interpolarse en el SQL en vez de enlazarse como parámetros.
Una ruta de campo controlada por el usuario en .filter(**{field: value}) no es automáticamente SQL Injection: Django resuelve los nombres de lookup como estructura de consulta. Aun así, puede exponer campos, relaciones o lookups que el endpoint no debería ofrecer, y provocar errores. Traduce las opciones externas de ordenación y filtro a una allow-list limitada de campos y lookups permitidos.
Cómo auditar consultas de Django
rg -n '(\.raw\(|RawSQL\(|\.extra\(|connection\.cursor\()' --glob '*.py' ..raw(), RawSQL, .extra() y connection.cursor(). Revisa cada coincidencia y sigue los valores hasta la consulta: parámetros enlazados, fragmentos SQL no confiables, identificadores o lookups dinámicos y plantillas personalizadas de Func.Preguntas frecuentes
QuerySet.extra() es una API heredada para añadir fragmentos SQL. Evítala en código nuevo y revisa con cuidado los argumentos where y select existentes. Prefiere expresiones de QuerySet; cuando necesites SQL sin procesar, enlaza valores con el argumento documentado params.
Django REST Framework no cambia la parametrización SQL del ORM. Las mismas reglas se aplican a consultas de vistas, serializers y viewsets; además, limita con una allow-list los campos de ordenación o filtro que controla el cliente.
Referencias
- Django docs — SQL injection protection
- flawopen.com — SQL Injection in Python
- Django docs — Performing raw SQL queries
- Django docs — Query expressions and RawSQL