flawopen.com/SQL Injection in PHP/Doctrine
Is Doctrine ORM safe from SQL injection?
Yes for DQL (Doctrine Query Language) with bound parameters and the QueryBuilder. Building a DQL or native SQL string with interpolated values reintroduces the same risk as any other ORM's raw-query escape hatch.
$em->createQuery( "SELECT u FROM User u " . "WHERE u.id = $userId" );
$qb->select('u')
->from('User', 'u')
->where('u.id = :id')
->setParameter('id', $userId);
The rule
setParameter() binds a value in the DQL query, which Doctrine translates into a bound parameter in the underlying SQL. The value stays separate from query syntax. Concatenating input into a DQL string passed to createQuery() bypasses that protection. The same rule applies to native SQL: Connection::executeQuery() is safe when placeholders and a parameter array are used, but unsafe when input is interpolated into the SQL string.
Bound values do not protect query structure
A placeholder represents a value, not a DQL identifier, SQL column, table name, operator, or sort direction. Doctrine’s ORM security guidance specifically warns that user input passed into QueryBuilder expression methods can still create injection. Keep those expressions in trusted code; map user-selected sort keys and directions through a strict allow-list.
$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);
- ✓Bind every external value with setParameter() or DBAL placeholders; never concatenate it into DQL or SQL.
- ✓Treat QueryBuilder expression methods and DQL fragments as code. Allow-list user-selectable fields, sort directions, operators, and function choices.
- ✓For native SQL, use executeQuery($sql, $params) or executeStatement($sql, $params) with placeholders.
- ✓Review both data flow and query structure. A parameterized value can still expose unauthorized records; enforce authorization separately.
How to check your codebase
rg -n -g '*.php' 'createQuery\(|orderBy\(|addOrderBy\(|executeQuery\(' .References
- Doctrine ORM docs — QueryBuilder parameters
- flawopen.com — SQL Injection in PHP
- Doctrine ORM docs — Security
- Doctrine DBAL docs — prepared statements and parameters