FlawOpen

flawopen.com/SQL Injection en Python/Django ORM

¿El ORM de Django protege frente a SQL Injection?

Página de referencia
Respuesta breve

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

INSEGURO — interpolar la entrada en SQL sin procesar
username = request.GET.get("u", "")
User.objects.raw(
    f"SELECT * FROM myapp_user WHERE username = '{username}'"
)
SEGURO — vincular la entrada con params de raw()
username = request.GET.get("u", "")
User.objects.raw(
    "SELECT * FROM myapp_user WHERE username = %s",
    [username],
)

Dónde requiere cuidado la protección

Model.objects.raw()

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.

Acceso directo con connection.cursor()

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.

Rutas de campo controladas por el usuario

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' .
Busca usos de .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

¿Es seguro extra()?

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.

¿También se aplica a Django REST Framework?

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

Véase también Inyección SQL (CWE-89): cómo funciona y cómo evitarla en cada lenguaje — flawopen.com Inyección SQL en Doctrine ORM: consultas seguras EF Core FromSqlRaw: SQL parametrizado seguro FastAPI y SQL Injection: qué protege Pydantic GORM Raw SQL: evita la inyección SQL en Go ¿Basta escapar comillas para evitar SQL Injection? ¿Basta con usar un ORM para prevenir la inyección SQL?