-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hash injection (security) #7310
Comments
This is definitely something we as a project are aware of, but I agree that a lot of our users might not be. In the best of all worlds, we should definitely do more to educate them :)!
How could we do that? Objects coming from req.query are plain objects, just like a user would write in their code (which is what makes this potentially so dangerous) I think the best solution for now would be to better inform our users - PRs that improve the documentation are very welcome! |
In Rails params hash inherits completely different class. But in Node you have no power over req.query parsing so... Start with a warning. I never liked this behavior in mongo in the first place - such magic is dangerous. |
@janmeier I don't see a way but enforcing people to use Something has to be done. .toString-ing all the input is very tedious job. Even if it's not direct vulnerability in the project, it has a lot of potential. Maybe make an announcement to users and see what do they think? |
Considering this is a security issue, this should not be autoclosed. |
@homakov This is interesting idea. One way to approach it will be instead of using strings for commands '$gt' will be to use Symbols. Some thing like - const {gt, lt, eq} = Sequelize.symbols;
Token.findOne({
token: req.query.token,
expiry: {[lt]: new Date()}
}); @janmeier maybe this can be implemented behind a feature flag and later activated by default so it won't be a breaking change. BTW @homakov not sure what frame work your using but I'm using hapi, which allows you pretty good control on user input. By default I don't allow any object properties to contain '$' if your not using hapi you can probably just use joi to quickly and easily sanitize your users input |
I think i was using express, filtering out $ is one way to go, but it's not a good practice to my best knowledge. |
I'm not sure if there is specific best practices for this. |
Is this vulnerability present in a feathers applications using JWT tokens and feathers-sequelize? All of my API endpoints first go through feathers, which authenticates, then parses and runs hooks (akin to express middleware) before passing data to sequelize. However, I suppose if I don't properly validate and sanitize my user input, this could still happen? |
@snewell92 This is not a vulnerability in Sequelize. If you are talking specifically about JWT one of the major feature of JWT is that you don't need to save them in the database (or anywhere else) since the token is signed and you can validate the data to be accurate. So unless feathers is doing something non standard it's JWT shouldn't reach the database layer. Always sanitize and validate user input |
Yes for sure sanitize + validate always. Feathers only temporarily stores JWT in local storage (or http only secure cookies), and of course revokes/removes the JWT after a time limit or logout, requiring another login to get a fresh token. Also, I do role checks in my service layer before passing stuff to sequelize, so that covers the data leaks and access (role information is embedded into the claims of the JWT, not based on query params). Thanks for the explanation / clarification! |
But that's on the client side right ? it doesn't need to store it anywhere on the server
Again if your not validating the actual input your at risk. So I do something like this if (!verifyJWT(req)) {
throw new Error('Not logged in');
}
return Post.findAll(req.query); this is works great I can send from the UI queries like -
Really nice and powerful.
Again I doubt anyone will be doing it that carelessly and most implementation will only let the client to control the |
Correct. only client side. In fact, its encouraged to not use cookies at all, and move towards statelessness. Feathers is built simply to be an API. Ideal for SPA's and api end points. However, in my case I use cookies because I have a multi page application that server-side renders the shell of all my pages. But since this is still a cookie - the JWT is never stored on the server in any way. The server merely inspects the cookie (httpOnly - can't stress this enough!) on the header. Because of that httpOnly option, client side javascript can't interact with it. Nice example, thanks! |
I think one way forward would be to export operator names in v4 and updating docs. Then people can transition to using const { eq } = Sequelize.operators;
where: {
[eq]: value
} and in v5 we can replace them all with Symbols. That said though, you always need to validate your user input types. And it's really not tedious: db.Token.findOne({
where: {
token: req.query.token + ''
}
); or |
You don't if that is what ORM is supposed to do. Readme also never says to cast all inputs manually. And it is tedious. |
The documentation tells you that the ORM provides an API to query specific properties with specific operators. If you want to use user input in that query syntax, but don't want to allow the user to provide all of the querying the ORM supports, you need to put a layer between the ORM and the user input. There is no way around that - the ORM can't just detect user input magically. It provides you an interface and will execute what you pass to it. You have to make sure you pass correct values. Converting these values from the serialised user input (HTTP request body, query parameters, ...) is not the job of an ORM. The ORM is one building block in your application and Express might be another. Wiring the interfaces up is the job of the developer, and that includes making sure invalid values are not mapped and only the subset of the ORM interface is (indirectly) accessible that you want to be (indirectly) accessible. |
Part "Basic" from http://docs.sequelizejs.com/manual/tutorial/querying.html doesn't teach to escape user input, but ORMs are built to work with user input, it's what they are all about. So one thing is documentation about shell.exec (user's job to validate) and another is findAll (something expected to be safe) |
You claim that "ORMs are built to work with user input", can you explain why you think so? ORMs are first and foremost build to map relations and objects, not to handle user input. You won't find "user input" mentioned anywhere on the ORM wikipedia page, because it's not its job. The ORM docs don't teach you how to validate user input just like the Of course, I would have absolutely no problem with some examples in the docs on how to validate user input and to avoid common caveats like this. If you wanna do a PR I would happily merge it 😉 |
…y symbols Operators will be represented internally by Symbols User can provide their own aliases by passing `options.operatorsAliases` Fix sequelize#7310
@sushantdhiman Is there problem in future if I define operator alias? |
@arifath Yes you are subjecting your application to hash injection as explained in this thread |
Using specially crafted requests we can trivially bypass secret_token protections on websites using sequalize.
Many people have code like this
But Node.js and other platforms allow nested parameters, ie token[$gt]=1 will turn into token = {"$gt":1}. When we pass such hash to sequalize it will consider it a query (greater than 1) and find the first token in DB, bypassing security of this endpoint. This behavior was copied from Mongo https://docs.mongodb.com/manual/reference/operator/query/
Using finely tuned $gt we can iterate all tokens in db and impersonate every single user.
There are vulnerable sites in the wild.
That's how it can be exploited in Mongo http://blog.websecurify.com/2014/08/hacking-nodejs-and-mongodb.html and https://cirw.in/blog/hash-injection
My advice would be to either disable this functionality entirely (i.e. require passing token {"$eq":token} every time) or make sure the parameter isn't coming from req.query and is native hash object.
The text was updated successfully, but these errors were encountered: