FlawOpen

flawopen.com/SQL Injection in PHP/Doctrine ORM

Doctrine ORMはSQLインジェクションを防げますか?

Reference page
要点

パラメーターを使ったDQLは安全です。DQLやネイティブSQLを文字列連結で作ると、通常の生クエリと同じ危険が戻ります。

危険 — 入力がDQL構文になる
$em->createQuery(
  "SELECT u FROM User u "
  . "WHERE u.id = $userId"
);
安全 — 信頼済みの式だけを使用
$qb->select('u')
  ->from('User', 'u')
  ->where('u.id = :id')
  ->setParameter('id', $userId);

バインド値ではクエリ構造を守れない

setParameter() はDQLの値をバインドし、Doctrineはクエリ文字列と分離したSQLパラメーターに変換します。createQuery() に入力を連結すると保護を迂回します。ネイティブSQLも同様です。Connection::executeQuery() はプレースホルダーと値配列を使えば安全ですが、SQL文字列に入力を埋め込むと危険です。

パラメーターで識別子は置き換えられない

プレースホルダーが表すのは値であり、DQL識別子、SQL列・テーブル名、演算子、ソート方向ではありません。ORMのセキュリティ資料は、QueryBuilderの式APIにユーザー入力を渡すとDQLインジェクションが起き得ると警告しています。式は信頼できるコードに置き、ソート候補は厳密な許可リストから選んでください。

危険 — 入力がDQL構文になる
$qb->orderBy(
  'u.' . $request->query->get('sort'),
  $request->query->get('direction')
);
安全 — 信頼済みの式だけを使用
$fields = [
    'name' => 'u.name',
    'created' => 'u.createdAt',
];
$directions = ['asc' => 'ASC', 'desc' => 'DESC'];

$key = (string) $request->query->get('sort', 'name');
$sort = $fields[$key] ?? $fields['name'];
$requestedDirection = strtolower(
    (string) $request->query->get('direction', 'asc')
);
$direction = $directions[$requestedDirection] ?? 'ASC';

$qb->orderBy($sort, $direction);

コードベースの確認方法

rg -n -g '*.php' 'createQuery\(|orderBy\(|addOrderBy\(|executeQuery\(' .
各結果について文字列連結、式APIへのユーザー入力、固定リスト外の識別子を確認します。検索結果はレビュー候補であり、悪用可能性の証明ではありません。

参考資料

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