fix: fix $bind and :repl being replaced in user input (v7)#14447
Conversation
WikiRik
left a comment
There was a problem hiding this comment.
New test looks good and other comments have also been addressed so I think this is good to go
|
There is one last fix I want to add before merging. |
|
My latest commit makes This change fixes a bug where the following query would throw because no value was provided for User.findAll({
where: literal(`id = ':id'`),
})I think this is the solution I can backport to v6 without introducing a breaking change. Hopefully. |
WikiRik
left a comment
There was a problem hiding this comment.
In general looks good, I have three comments about the tests. Same comments also apply to their respective equivalent for positional replacements
WikiRik
left a comment
There was a problem hiding this comment.
Thanks again for your thorough work on this!
|
Now onto v6 |
|
🎉 This PR is included in version 7.0.0-alpha.12 🎉 The release is available on: Your semantic-release bot 📦🚀 |
…lize#14447) BREAKING CHANGE: There is no need to escape $bind parameters in strings anymore, bind parameters & replacements are only treated as such where an SQL value can be used.
This PR is still WIP but I'm opening it to describe the changes as I go.
Fixes in v7: #13817
Closes #14273
Closes #12523
This (v7) PR fixes how bind & replacements are handled in query interface & findAll methods.
Before this PR
Let's take the following query:
Before this PR, Sequelize would first generate the SQl for the
whereparameter:Then pass that query to
Sequelize#query, which would process thereplacementsoption (in this case, the:firstNameparameter):Because of this two-step processing, a malicious input can result in SQL injection. For instance with these two user inputs:
With the above input,
QueryGeneratorwill first generate this:Then
Sequelize#querywill process:firstName, resulting in this:Resulting in the injection of
DROP TABLE users;, interpreted as actual SQL.After this PR
Replacements are now handled by
QueryGeneratorinstead. This allowsQueryGeneratorto only process replacements inliteral()and nowhere else.QueryGeneratorwill generate this:Then the query will be passed to a new method,
Sequelize#queryRaw, which does not process replacements at all. So the query is executed as-is.Sequelize#querywill still process replacements, but as long as you don't put replacements inside strings there, and don't inline user input yourself, you'll be fine.Escaping
$paramsIt is not necessary to escape
$bindparameters in string anymore. This solution was a horrible band aid that caused more issues than it solved.Only bind parameters outside of strings and comments will be treated as bind parameters by Sequelize.
Example 1 (mysql):
Example 1 (mysql):
Other changes
bind&replacementoptions can now be used at the same time! This is because handling of bind parameters is done by a new parser that knows whether$nameis a bind parameter, or just text inside a string.bindparameters inupdate, andinsertmethods. It was not previously possible because sequelize generates its own bind parameters in these methods, which would overwrite the ones provided by the user. They are now safely merged.::castis not interpreted as a replacement anymore, it's a cast.TODO
bindreplacer against commentsTODO (future PR)