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);
- ✓外部値はすべて setParameter() またはDBALのプレースホルダーで渡し、DQL/SQLに連結しないでください。
- ✓QueryBuilderの式APIとDQL断片はコードとして扱います。フィールド、順序、演算子、関数は許可リストで制限してください。
- ✓ネイティブSQLにはプレースホルダー付きの executeQuery($sql, $params) または executeStatement($sql, $params) を使います。
- ✓データフローとクエリ構造の両方を確認します。パラメーター化しても未許可レコードを公開する可能性があるため、認可を別途実施してください。
コードベースの確認方法
rg -n -g '*.php' 'createQuery\(|orderBy\(|addOrderBy\(|executeQuery\(' .各結果について文字列連結、式APIへのユーザー入力、固定リスト外の識別子を確認します。検索結果はレビュー候補であり、悪用可能性の証明ではありません。