FlawOpen

flawopen.com/PythonのSQL Injection/Django ORM

Django ORMはデフォルトでSQL Injectionを防げる?

リファレンスページ
要点

.filter()、.get()、.exclude()など一般的なQuerySet APIは、SQLと値を分離し、値をデータベースドライバーへパラメーターとして渡します。ただし、あらゆるクエリー構築方法が保護されるわけではありません。raw SQL、カスタム式、ユーザーが選ぶクエリー構造は個別に確認します。

Django QuerySetで値を安全に扱う

危険 — raw SQL に入力を埋め込む
username = request.GET.get("u", "")
User.objects.raw(
    f"SELECT * FROM myapp_user WHERE username = '{username}'"
)
安全 — raw() の params で入力をバインドする
username = request.GET.get("u", "")
User.objects.raw(
    "SELECT * FROM myapp_user WHERE username = %s",
    [username],
)

保護の境界を確認する

Model.objects.raw()

Manager.raw()は指定されたSQLを実行します。ユーザー制御値はparamsで渡し、プレースホルダーを引用符で囲まないでください。例:User.objects.raw("SELECT * FROM users WHERE username = %s", [username])。f-string、%書式、文字列連結で値をSQLに埋め込まないでください。パラメーターは値用であり、テーブル名や列名などSQL識別子には使えません。

connection.cursor()による直接アクセス

DBへ直接アクセスする場合はcursor.execute(sql, params)を使い、Djangoの仕様に沿って値を分けて渡します。sqlに値を補間してはいけません。RawSQLやFunc式も確認してください。テンプレートコンテキストやキーワード引数はパラメーターとして束縛されず、SQLに補間されることがあります。

ユーザーが指定するフィールドパス

.filter(**{field: value})のフィールドパスがユーザー制御でも、自動的にSQL Injectionになるわけではありません。Djangoはlookup名をクエリー構造として解決します。ただし、endpointが公開すべきでないフィールド、関連、lookupを露出したり、エラーを起こしたりする可能性があります。外部の並べ替え・絞り込み項目を許可リストのフィールドとlookupに変換してください。

Djangoのクエリーを監査する

rg -n '(\.raw\(|RawSQL\(|\.extra\(|connection\.cursor\()' --glob '*.py' .
.raw()、RawSQL、.extra()、connection.cursor()の使用箇所を検索します。各箇所で値の流れを確認し、パラメーター束縛、未信頼SQL断片、動的識別子やlookup、カスタムFuncテンプレートを調べます。

よくある質問

extra()は安全?

QuerySet.extra()はraw SQL断片を追加する旧APIです。新規コードでは避け、既存のwhereとselect引数を慎重に確認してください。QuerySet式を優先し、raw SQLが必要な場合は文書化されたparams引数で値を束縛します。

Django REST Frameworkにも当てはまる?

Django REST FrameworkもORMのSQLパラメーター化を変えません。view、serializer、viewsetからのクエリーにも同じルールが適用されます。クライアントが指定する並べ替えやフィルター項目も許可リストで制限してください。

参考資料

関連項目 SQL インジェクション(CWE-89)の仕組みと各言語での防ぎ方 — flawopen.com Doctrine ORMのSQLインジェクション対策 EF Core FromSqlRaw:安全なSQLパラメーター化 FastAPIのSQLインジェクション:Pydanticが防ぐこと・防げないこと GORM Raw SQL:GoでSQLインジェクションを防ぐ 引用符のエスケープだけでSQL Injectionを防げる? ORM を使えば SQL インジェクションを防げるか