Hi everyone 👋
We have recently released Sequelize 6.19.1 (since patched as 6.19.2) which includes a breaking change.
We normally want to fully respect semver rules, unfortunately a very serious SQL injection exploit has surfaced and it was impossible for us to fix it without introducing a breaking change. We considered the issue serious enough to break the rule and release the fix in V6. Most of you are going to be completely unaffected, but a small percentage of users may experience breakage with this version, more below
The exploit
The SQL injection exploit is related to replacements. Here is such an example:
In the following query, some parameters are passed through replacements, and some are passed directly through the where option.
User.findAll({
where: or(
literal('soundex("firstName") = soundex(:firstName)'),
{ lastName: lastName },
),
replacements: { firstName },
})
This is a very legitimate use case, but this query was vulnerable to SQL injection due to how Sequelize processed the query: Sequelize built a first query using the where option, then passed it over to sequelize.query which parsed the resulting SQL to inject all :replacements.
If the user passed values such as
{
"firstName": "OR true; DROP TABLE users;",
"lastName": ":firstName"
}
Sequelize would first generate this query:
SELECT * FROM users WHERE soundex("firstName") = soundex(:firstName) OR "lastName" = ':firstName'
Then would inject replacements in it, which resulted in this:
SELECT * FROM users WHERE soundex("firstName") = soundex('OR true; DROP TABLE users;') OR "lastName" = ''OR true; DROP TABLE users;''
As you can see this resulted in arbitrary user-provided SQL being executed.
The solution & breaking change
The way to fix this without breaking everyone's codebase was to use a smarter SQL parser that would only inject replacements in places where native SQL parameters are allowed (with some safe exceptions to stay as backward compatible as possible).
This means that :replacements will not be treated as such anymore if they are quoted, as we treat it as being part of a SQL string.
Details here: https://github.com/sequelize/sequelize/blob/v6/test/unit/utils/sql.test.js
On top of this, in Sequelize 7, we rewrote this system completely to handle both steps at the same time. Removing all risk of SQL injection (#14447).
Some of you relied on this to inject parameters inside of sql strings, which is why this can be considered a breaking change.
Note however that doing that was not safer than simply concatenating, and it being done by us may have given you a false sense of security regarding input sanitization.
Once again, we are very sorry for the inconvenience this causes.
- Zoé
Hi everyone 👋
We have recently released Sequelize 6.19.1 (since patched as 6.19.2) which includes a breaking change.
We normally want to fully respect semver rules, unfortunately a very serious SQL injection exploit has surfaced and it was impossible for us to fix it without introducing a breaking change. We considered the issue serious enough to break the rule and release the fix in V6. Most of you are going to be completely unaffected, but a small percentage of users may experience breakage with this version, more below
The exploit
The SQL injection exploit is related to replacements. Here is such an example:
In the following query, some parameters are passed through replacements, and some are passed directly through the
whereoption.This is a very legitimate use case, but this query was vulnerable to SQL injection due to how Sequelize processed the query: Sequelize built a first query using the
whereoption, then passed it over tosequelize.querywhich parsed the resulting SQL to inject all:replacements.If the user passed values such as
{ "firstName": "OR true; DROP TABLE users;", "lastName": ":firstName" }Sequelize would first generate this query:
Then would inject replacements in it, which resulted in this:
As you can see this resulted in arbitrary user-provided SQL being executed.
The solution & breaking change
The way to fix this without breaking everyone's codebase was to use a smarter SQL parser that would only inject replacements in places where native SQL parameters are allowed (with some safe exceptions to stay as backward compatible as possible).
This means that
:replacementswill not be treated as such anymore if they are quoted, as we treat it as being part of a SQL string.Details here: https://github.com/sequelize/sequelize/blob/v6/test/unit/utils/sql.test.js
On top of this, in Sequelize 7, we rewrote this system completely to handle both steps at the same time. Removing all risk of SQL injection (#14447).
Some of you relied on this to inject parameters inside of sql strings, which is why this can be considered a breaking change.
Note however that doing that was not safer than simply concatenating, and it being done by us may have given you a false sense of security regarding input sanitization.
Once again, we are very sorry for the inconvenience this causes.
- Zoé