Skip to content

Commit 4ec8b3f

Browse files
committed
Python: Model map_reduce
1 parent 7c085ec commit 4ec8b3f

File tree

3 files changed

+34
-2
lines changed

3 files changed

+34
-2
lines changed

python/ql/lib/semmle/python/frameworks/NoSQL.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,26 @@ private module NoSql {
132132
override predicate vulnerableToStrings() { none() }
133133
}
134134

135+
private class MongoMapReduce extends API::CallNode, NoSqlQuery::Range {
136+
MongoMapReduce() { this = mongoCollection().getMember("map_reduce").getACall() }
137+
138+
override DataFlow::Node getQuery() { result in [this.getArg(0), this.getArg(1)] }
139+
140+
override predicate interpretsDict() { none() }
141+
142+
override predicate vulnerableToStrings() { any() }
143+
}
144+
145+
private class MongoMapReduceQuery extends API::CallNode, NoSqlQuery::Range {
146+
MongoMapReduceQuery() { this = mongoCollection().getMember("map_reduce").getACall() }
147+
148+
override DataFlow::Node getQuery() { result in [this.getArgByName("query")] }
149+
150+
override predicate interpretsDict() { any() }
151+
152+
override predicate vulnerableToStrings() { none() }
153+
}
154+
135155
/** The `$where` query operator executes a string as JavaScript. */
136156
private class WhereQueryOperator extends DataFlow::Node, Decoding::Range {
137157
API::Node dictionary;

python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/NoSqlInjection.expected

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ edges
44
| PoC/server.py:1:26:1:32 | GSSA Variable request | PoC/server.py:42:14:42:20 | ControlFlowNode for request |
55
| PoC/server.py:1:26:1:32 | GSSA Variable request | PoC/server.py:51:14:51:20 | ControlFlowNode for request |
66
| PoC/server.py:1:26:1:32 | GSSA Variable request | PoC/server.py:76:14:76:20 | ControlFlowNode for request |
7+
| PoC/server.py:1:26:1:32 | GSSA Variable request | PoC/server.py:96:14:96:20 | ControlFlowNode for request |
78
| PoC/server.py:26:5:26:17 | SSA variable author_string | PoC/server.py:27:25:27:37 | ControlFlowNode for author_string |
89
| PoC/server.py:26:21:26:27 | ControlFlowNode for request | PoC/server.py:26:5:26:17 | SSA variable author_string |
910
| PoC/server.py:27:5:27:10 | SSA variable author | PoC/server.py:30:27:30:44 | ControlFlowNode for Dict |
@@ -21,6 +22,9 @@ edges
2122
| PoC/server.py:79:23:79:101 | ControlFlowNode for BinaryExpr | PoC/server.py:85:37:85:47 | ControlFlowNode for accumulator |
2223
| PoC/server.py:83:5:83:9 | SSA variable group | PoC/server.py:90:29:90:47 | ControlFlowNode for Dict |
2324
| PoC/server.py:85:37:85:47 | ControlFlowNode for accumulator | PoC/server.py:83:5:83:9 | SSA variable group |
25+
| PoC/server.py:96:5:96:10 | SSA variable author | PoC/server.py:97:5:97:10 | SSA variable mapper |
26+
| PoC/server.py:96:14:96:20 | ControlFlowNode for request | PoC/server.py:96:5:96:10 | SSA variable author |
27+
| PoC/server.py:97:5:97:10 | SSA variable mapper | PoC/server.py:100:9:100:14 | ControlFlowNode for mapper |
2428
| flask_mongoengine_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_mongoengine_bad.py:1:26:1:32 | GSSA Variable request |
2529
| flask_mongoengine_bad.py:1:26:1:32 | GSSA Variable request | flask_mongoengine_bad.py:19:21:19:27 | ControlFlowNode for request |
2630
| flask_mongoengine_bad.py:1:26:1:32 | GSSA Variable request | flask_mongoengine_bad.py:26:21:26:27 | ControlFlowNode for request |
@@ -121,6 +125,10 @@ nodes
121125
| PoC/server.py:83:5:83:9 | SSA variable group | semmle.label | SSA variable group |
122126
| PoC/server.py:85:37:85:47 | ControlFlowNode for accumulator | semmle.label | ControlFlowNode for accumulator |
123127
| PoC/server.py:90:29:90:47 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
128+
| PoC/server.py:96:5:96:10 | SSA variable author | semmle.label | SSA variable author |
129+
| PoC/server.py:96:14:96:20 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
130+
| PoC/server.py:97:5:97:10 | SSA variable mapper | semmle.label | SSA variable mapper |
131+
| PoC/server.py:100:9:100:14 | ControlFlowNode for mapper | semmle.label | ControlFlowNode for mapper |
124132
| flask_mongoengine_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
125133
| flask_mongoengine_bad.py:1:26:1:32 | GSSA Variable request | semmle.label | GSSA Variable request |
126134
| flask_mongoengine_bad.py:19:5:19:17 | SSA variable unsafe_search | semmle.label | SSA variable unsafe_search |
@@ -207,6 +215,7 @@ subpaths
207215
| PoC/server.py:46:27:46:68 | ControlFlowNode for Dict | PoC/server.py:1:26:1:32 | ControlFlowNode for ImportMember | PoC/server.py:46:27:46:68 | ControlFlowNode for Dict | This NoSQL query contains an unsanitized $@. | PoC/server.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
208216
| PoC/server.py:60:27:60:58 | ControlFlowNode for Dict | PoC/server.py:1:26:1:32 | ControlFlowNode for ImportMember | PoC/server.py:60:27:60:58 | ControlFlowNode for Dict | This NoSQL query contains an unsanitized $@. | PoC/server.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
209217
| PoC/server.py:90:29:90:47 | ControlFlowNode for Dict | PoC/server.py:1:26:1:32 | ControlFlowNode for ImportMember | PoC/server.py:90:29:90:47 | ControlFlowNode for Dict | This NoSQL query contains an unsanitized $@. | PoC/server.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
218+
| PoC/server.py:100:9:100:14 | ControlFlowNode for mapper | PoC/server.py:1:26:1:32 | ControlFlowNode for ImportMember | PoC/server.py:100:9:100:14 | ControlFlowNode for mapper | This NoSQL query contains an unsanitized $@. | PoC/server.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
210219
| flask_mongoengine_bad.py:22:34:22:44 | ControlFlowNode for json_search | flask_mongoengine_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_mongoengine_bad.py:22:34:22:44 | ControlFlowNode for json_search | This NoSQL query contains an unsanitized $@. | flask_mongoengine_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
211220
| flask_mongoengine_bad.py:30:39:30:59 | ControlFlowNode for Dict | flask_mongoengine_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_mongoengine_bad.py:30:39:30:59 | ControlFlowNode for Dict | This NoSQL query contains an unsanitized $@. | flask_mongoengine_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
212221
| flask_pymongo_bad.py:14:31:14:51 | ControlFlowNode for Dict | flask_pymongo_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_pymongo_bad.py:14:31:14:51 | ControlFlowNode for Dict | This NoSQL query contains an unsanitized $@. | flask_pymongo_bad.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |

python/ql/test/query-tests/Security/CWE-943-NoSqlInjection/PoC/server.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,14 @@ def by_map_reduce():
9696
author = request.args['author']
9797
mapper = 'function() { emit(this.author, this.author === "'+author+'") }'
9898
reducer = "function(key, values) { return values.some( x => x ) }"
99-
results = posts.map_reduce(mapper, reducer, "results")
99+
results = posts.map_reduce(
100+
mapper, # $ result=BAD
101+
reducer, # $ result=OK
102+
"results")
100103
# Use `" | "a" === "a` as author
101104
# making the query `this.author === "" | "a" === "a"`
102105
# Found by http://127.0.0.1:5000/byMapReduce?author=%22%20|%20%22a%22%20===%20%22a
103-
post = results.find_one({'value': True}) # $ MISSING: result=BAD
106+
post = results.find_one({'value': True})
104107
if(post):
105108
post["author"] = post["_id"]
106109
return show_post(post, author)

0 commit comments

Comments
 (0)