Is SQL injection possible via literal() in an ORM query?
An ORM query (Sequelize) mixes a raw literal() SQL fragment with replacements. Decide whether SQL injection is possible and explain why.
Constraints:
firstNameis attacker-controlled and reaches theliteral()text- assume an outdated ORM version with known parsing bugs
User.findAll({
where: or(literal(`soundex("firstName") = soundex(:firstName)`),
{ lastName }),
replacements: { firstName },
});
Diagnose the cause.
Yes — literal() is a raw SQL escape hatch; the ORM does NOT parameterize text inside it. The :firstName binding is only substituted by string replacement, and on an outdated version a crafted firstName (and lastName set to :firstName) breaks out of the literal and injects SQL. Fix: never put user input near literal(); rely on the ORM's parameterized where operators and upgrade the library.
- ✗Thinking the ORM parameterizes raw SQL inside
literal() - ✗Believing the presence of
replacementsmakes the whole query safe - ✗Mistaking the injection via literal for a performance bug
- →Why do
replacementsnot protect the text passed intoliteral()? - →When is using
literal()in an ORM query ever justified?
The vulnerability
literal() is raw SQL the ORM inserts as-is. Parameterization (replacements) only covers ORM binds — it does NOT protect text inside literal():
where: or(literal(`soundex("firstName") = soundex(:firstName)`), { lastName })
On an outdated Sequelize version the :firstName substitution is a string replacement. Payload:
{"firstName":"OR true; DROP TABLE users;","lastName":":firstName"}
breaks out of the literal → SQL injection.
The fix
User.findAll({ where: { firstName, lastName } });
✅ The ORM's parameterized where operators escape input. ⚠️ Keep user input away from literal(); upgrade the library.