-
Notifications
You must be signed in to change notification settings - Fork 2k
Python: promote nosql query #14070
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
Python: promote nosql query #14070
Changes from 1 commit
60dc1af
55707d3
db04597
087961d
19046ea
bf8bfd9
114984b
c0b3245
7edebbe
f253f97
970e881
b07d085
d91cd21
154a369
d9f63e1
a063d7d
4614b1a
5611bda
30c37ca
7c085ec
4ec8b3f
12dab88
8156fa9
37a4f35
3fb579e
d90630a
c2b6383
9682c82
2a739b3
eb1be08
2a7b593
a8e0023
d5b64c5
3043633
2e028a4
74d6f37
2d845e3
e170805
f3a0161
9769668
16e1a00
d7ad5a0
3676262
d6d13f8
9b73bbf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
Query operators that interpret JavaScript are no longer considered sinks. Instead they are considered decodings and the output is the tainted dictionary. The state changes to `DictInput` to reflect that the user now controls a dangerous dictionary. This fixes the spurious result and moves the error reporting to a more logical place.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,40 +123,49 @@ private module NoSql { | |
| } | ||
|
|
||
| /** The `$where` query operator executes a string as JavaScript. */ | ||
| private class WhereQueryOperator extends API::CallNode, NoSqlQuery::Range { | ||
| private class WhereQueryOperator extends DataFlow::Node, Decoding::Range { | ||
| API::Node dictionary; | ||
| DataFlow::Node query; | ||
|
|
||
| WhereQueryOperator() { | ||
| this = mongoCollection().getMember(mongoCollectionMethodName()).getACall() and | ||
| query = this.getParameter(0).getSubscript("$where").asSink() | ||
| dictionary = | ||
| mongoCollection().getMember(mongoCollectionMethodName()).getACall().getParameter(0) and | ||
| query = dictionary.getSubscript("$where").asSink() and | ||
| this = dictionary.asSink() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't quite know what to think about this... I wasn't quite sure if either the old or the new these solutions could handle when the dictionary is not constructed inside the argument to the call, such as: search = {'$where': 'this.author === "'+author+'"'}
post = posts.find_one(search) # $ result=BADBut I tried it out locally, and it did in fact work. I guess they would since the So basically you seem to be creating an additional taint-step specifically for NoSQL injection directly from the element of a dictionary with key I think that works great for simple examples, but if there is any interprocedural steps, it will not be so nice. So I think we need to track these things with data-flow, so it can be part of the path shown to end-users. We might be able to achieve that with a string->dict taint-step directly when constructing a dictionary with a You could apply the same logic to the decoded = json.loads(user_controlled)
search = {
"body": decoded,
"args": [ "$author" ],
"lang": "js"
}
posts.find_one({'$expr': {'$function': search}}) # $ result=BAD
posts.find_one({'$expr': {'$function': decoded}}) # $ result=BAD
posts.find_one({'$expr': decoded}) # $ result=BAD
posts.find_one(decoded) # $ result=BADAdding query specific taint-steps that mutate flow-state doesn't play very nicely with using Concepts, so I understand the temptation to just reuse the decoding Concepts, but I would argue that in fact no Decoding is taking place here. What I've done previously is to add query specific logic to the library modeling file, which is certainly something we could also do here (example from flask, originally added in #6989)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is basically what we are doing. We just require that the dictionary also flows to a harmful place. Anyway, I added the above tests, and they all pass out of the box...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Nice 👍 I guess that comes from the fact that we say after a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still think we could end up with a very long jumpstep due to the way it's written right now, which I don't think is a good solution.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that is a reasonable objection (especially now that we got better path explanations in general). I made the step target the created dictionary instead of the argument that the dictionary flows to. Dataflow is able to link the two for us. |
||
| } | ||
|
|
||
| override DataFlow::Node getQuery() { result = query } | ||
| override DataFlow::Node getAnInput() { result = query } | ||
|
|
||
| override predicate interpretsDict() { none() } | ||
| override DataFlow::Node getOutput() { result = this } | ||
|
|
||
| override predicate vulnerableToStrings() { any() } | ||
| override string getFormat() { result = "NoSQL" } | ||
|
|
||
| override predicate mayExecuteInput() { any() } | ||
|
yoff marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| /** The `$function` query operator executes its `body` string as JavaScript. */ | ||
| private class FunctionQueryOperator extends API::CallNode, NoSqlQuery::Range { | ||
| private class FunctionQueryOperator extends DataFlow::Node, Decoding::Range { | ||
| API::Node dictionary; | ||
|
|
||
| DataFlow::Node query; | ||
|
|
||
| FunctionQueryOperator() { | ||
| this = mongoCollection().getMember(mongoCollectionMethodName()).getACall() and | ||
| query = | ||
| this.getParameter(0) | ||
| .getASubscript*() | ||
| .getSubscript("$function") | ||
| .getSubscript("body") | ||
| .asSink() | ||
| dictionary = | ||
| mongoCollection() | ||
| .getMember(mongoCollectionMethodName()) | ||
| .getACall() | ||
| .getParameter(0) | ||
| .getASubscript*() and | ||
| query = dictionary.getSubscript("$function").getSubscript("body").asSink() and | ||
| this = dictionary.asSink() | ||
| } | ||
|
|
||
| override DataFlow::Node getQuery() { result = query } | ||
| override DataFlow::Node getAnInput() { result = query } | ||
|
|
||
| override DataFlow::Node getOutput() { result = this } | ||
|
|
||
| override predicate interpretsDict() { none() } | ||
| override string getFormat() { result = "NoSQL" } | ||
|
|
||
| override predicate vulnerableToStrings() { any() } | ||
| override predicate mayExecuteInput() { any() } | ||
| } | ||
|
|
||
| /** | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.