FlawOpen

flawopen.com/SQL Injection en Python/Django ORM

L’ORM Django protège-t-il contre SQL Injection ?

Page de référence
Réponse courte

L’API QuerySet de Django sépare le SQL des valeurs et transmet celles-ci comme paramètres au pilote de base de données pour les requêtes courantes, telles que .filter(), .get() et .exclude(). Cette protection ne couvre pas toutes les façons de construire une requête : SQL brut, expressions personnalisées et structure choisie par l’utilisateur nécessitent un examen distinct.

Utiliser les valeurs en sécurité avec les QuerySets Django

NON SÛR — interpoler l’entrée dans le SQL brut
username = request.GET.get("u", "")
User.objects.raw(
    f"SELECT * FROM myapp_user WHERE username = '{username}'"
)
SÛR — lier l’entrée avec params dans raw()
username = request.GET.get("u", "")
User.objects.raw(
    "SELECT * FROM myapp_user WHERE username = %s",
    [username],
)

Les limites de la protection

Model.objects.raw()

Manager.raw() exécute le SQL fourni. Transmettez les valeurs contrôlées par l’utilisateur via params et laissez les marqueurs sans guillemets, par exemple User.objects.raw("SELECT * FROM users WHERE username = %s", [username]). N’insérez pas de valeurs avec une f-string, le formatage % ou la concaténation. Les paramètres représentent des valeurs, pas des identifiants SQL comme les noms de tables ou de colonnes.

Accès direct avec connection.cursor()

Pour accéder directement à la base, utilisez cursor.execute(sql, params) et transmettez les valeurs séparément avec les marqueurs documentés par Django. N’interpolez pas de valeurs dans sql. Examinez aussi RawSQL et les expressions Func : le contexte du modèle et les arguments nommés peuvent être interpolés dans le SQL au lieu d’être liés comme paramètres.

Chemins de champs contrôlés par l’utilisateur

Un chemin de champ contrôlé par l’utilisateur dans .filter(**{field: value}) n’est pas automatiquement une SQL Injection : Django résout les noms de lookup comme structure de requête. Il peut toutefois exposer des champs, relations ou lookups que l’endpoint ne devrait pas proposer, et provoquer des erreurs. Convertissez les choix externes de tri et de filtre en liste d’autorisation limitée de champs et lookups.

Auditer les requêtes Django

rg -n '(\.raw\(|RawSQL\(|\.extra\(|connection\.cursor\()' --glob '*.py' .
Recherchez les usages de .raw(), RawSQL, .extra() et connection.cursor(). Examinez chaque occurrence et remontez les valeurs jusqu’à la requête : paramètres liés, fragments SQL non fiables, identifiants ou lookups dynamiques, et modèles personnalisés Func.

Questions fréquentes

extra() est-il sûr ?

QuerySet.extra() est une API historique qui ajoute des fragments SQL. Évitez-la dans le nouveau code et examinez attentivement ses arguments where et select. Préférez les expressions QuerySet ; si le SQL brut est nécessaire, liez les valeurs avec l’argument documenté params.

Cela s’applique-t-il aussi à Django REST Framework ?

Django REST Framework ne change pas la paramétrisation SQL de l’ORM. Les mêmes règles s’appliquent aux requêtes issues des vues, serializers et viewsets ; limitez aussi par une liste d’autorisation les champs de tri ou de filtre contrôlés par le client.

Références

À voir aussi Injection SQL (CWE-89) : fonctionnement et prévention dans chaque langage — flawopen.com Injection SQL dans Doctrine ORM : requêtes sûres EF Core FromSqlRaw : SQL paramétré sûr FastAPI et SQL Injection : limites de Pydantic GORM Raw SQL : éviter l’injection SQL en Go Échapper les guillemets suffit-il contre SQL Injection ? Utiliser un ORM suffit-il à prévenir l'injection SQL ?