Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7722d77

Browse files
committed
JS: add the NoSQL $where as a sink for js/code-injection
1 parent 20cf044 commit 7722d77

7 files changed

Lines changed: 94 additions & 1 deletion

File tree

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
| Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional command execution calls. |
2626
| Expression has no effect (`js/useless-expression`) | Less results | This query no longer flags an expression when that expression is the only content of the containing file. |
2727
| Unknown directive (`js/unknown-directive`) | Less results | This query no longer flags directives generated by the Babel compiler. |
28+
| Code injection (`js/code-injection`) | More results | More potential vulnerabilities involving NoSQL code operators are now recognized. |
2829

2930
## Changes to libraries
3031

javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@ import javascript
66

77
module NoSQL {
88
/** An expression that is interpreted as a NoSQL query. */
9-
abstract class Query extends Expr { }
9+
abstract class Query extends Expr {
10+
/** Gets an expression that is interpreted as a code operator in this query. */
11+
DataFlow::Node getACodeOperator() { none() }
12+
}
13+
}
14+
15+
/**
16+
* Gets the value of a `$where` property of an object that flows to `n`.
17+
*/
18+
private DataFlow::Node getADollarWherePropertyValue(DataFlow::Node n) {
19+
result = n.getALocalSource().getAPropertyWrite("$where").getRhs()
1020
}
1121

1222
/**
@@ -190,6 +200,10 @@ private module MongoDB {
190200
*/
191201
class Query extends NoSQL::Query {
192202
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
203+
204+
override DataFlow::Node getACodeOperator() {
205+
result = getADollarWherePropertyValue(this.flow())
206+
}
193207
}
194208
}
195209

@@ -678,6 +692,10 @@ private module Mongoose {
678692
*/
679693
class MongoDBQueryPart extends NoSQL::Query {
680694
MongoDBQueryPart() { any(InvokeNode call).interpretsArgumentAsQuery(this.flow()) }
695+
696+
override DataFlow::Node getACodeOperator() {
697+
result = getADollarWherePropertyValue(this.flow())
698+
}
681699
}
682700

683701
/**
@@ -763,6 +781,10 @@ private module Minimongo {
763781
*/
764782
class Query extends NoSQL::Query {
765783
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
784+
785+
override DataFlow::Node getACodeOperator() {
786+
result = getADollarWherePropertyValue(this.flow())
787+
}
766788
}
767789
}
768790

@@ -809,5 +831,9 @@ private module MarsDB {
809831
*/
810832
class Query extends NoSQL::Query {
811833
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
834+
835+
override DataFlow::Node getACodeOperator() {
836+
result = getADollarWherePropertyValue(this.flow())
837+
}
812838
}
813839
}

javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ abstract class HeuristicSink extends DataFlow::Node { }
2525

2626
private class HeuristicCodeInjectionSink extends HeuristicSink, CodeInjection::Sink {
2727
HeuristicCodeInjectionSink() {
28+
isAssignedTo(this, "$where")
29+
or
2830
isAssignedToOrConcatenatedWith(this, "(?i)(command|cmd|exec|code|script|program)")
2931
or
3032
isArgTo(this, "(?i)(eval|run)") // "exec" clashes too often with `RegExp.prototype.exec`

javascript/ql/src/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,11 @@ module CodeInjection {
115115
)
116116
}
117117
}
118+
119+
/**
120+
* A code operator of a NoSQL query as a code injection sink.
121+
*/
122+
class NoSQLCodeInjectionSink extends Sink {
123+
NoSQLCodeInjectionSink() { any(NoSQL::Query q).getACodeOperator() = this }
124+
}
118125
}

javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
nodes
2+
| NoSQLCodeInjection.js:18:24:18:31 | req.body |
3+
| NoSQLCodeInjection.js:18:24:18:31 | req.body |
4+
| NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
5+
| NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
6+
| NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name |
7+
| NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name |
8+
| NoSQLCodeInjection.js:19:36:19:43 | req.body |
9+
| NoSQLCodeInjection.js:19:36:19:43 | req.body |
10+
| NoSQLCodeInjection.js:19:36:19:48 | req.body.name |
211
| angularjs.js:10:22:10:29 | location |
312
| angularjs.js:10:22:10:29 | location |
413
| angularjs.js:10:22:10:36 | location.search |
@@ -108,6 +117,14 @@ nodes
108117
| tst.js:26:26:26:53 | locatio ... ring(1) |
109118
| tst.js:26:26:26:53 | locatio ... ring(1) |
110119
edges
120+
| NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
121+
| NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
122+
| NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
123+
| NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
124+
| NoSQLCodeInjection.js:19:36:19:43 | req.body | NoSQLCodeInjection.js:19:36:19:48 | req.body.name |
125+
| NoSQLCodeInjection.js:19:36:19:43 | req.body | NoSQLCodeInjection.js:19:36:19:48 | req.body.name |
126+
| NoSQLCodeInjection.js:19:36:19:48 | req.body.name | NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name |
127+
| NoSQLCodeInjection.js:19:36:19:48 | req.body.name | NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name |
111128
| angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search |
112129
| angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search |
113130
| angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search |
@@ -212,6 +229,8 @@ edges
212229
| tst.js:26:26:26:40 | location.search | tst.js:26:26:26:53 | locatio ... ring(1) |
213230
| tst.js:26:26:26:40 | location.search | tst.js:26:26:26:53 | locatio ... ring(1) |
214231
#select
232+
| NoSQLCodeInjection.js:18:24:18:37 | req.body.query | NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query | $@ flows to here and is interpreted as code. | NoSQLCodeInjection.js:18:24:18:31 | req.body | User-provided value |
233+
| NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name | NoSQLCodeInjection.js:19:36:19:43 | req.body | NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name | $@ flows to here and is interpreted as code. | NoSQLCodeInjection.js:19:36:19:43 | req.body | User-provided value |
215234
| angularjs.js:10:22:10:36 | location.search | angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:10:22:10:29 | location | User-provided value |
216235
| angularjs.js:13:23:13:37 | location.search | angularjs.js:13:23:13:30 | location | angularjs.js:13:23:13:37 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:13:23:13:30 | location | User-provided value |
217236
| angularjs.js:16:28:16:42 | location.search | angularjs.js:16:28:16:35 | location | angularjs.js:16:28:16:42 | location.search | $@ flows to here and is interpreted as code. | angularjs.js:16:28:16:35 | location | User-provided value |

javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
nodes
2+
| NoSQLCodeInjection.js:18:24:18:31 | req.body |
3+
| NoSQLCodeInjection.js:18:24:18:31 | req.body |
4+
| NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
5+
| NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
6+
| NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name |
7+
| NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name |
8+
| NoSQLCodeInjection.js:19:36:19:43 | req.body |
9+
| NoSQLCodeInjection.js:19:36:19:43 | req.body |
10+
| NoSQLCodeInjection.js:19:36:19:48 | req.body.name |
211
| angularjs.js:10:22:10:29 | location |
312
| angularjs.js:10:22:10:29 | location |
413
| angularjs.js:10:22:10:36 | location.search |
@@ -112,6 +121,14 @@ nodes
112121
| tst.js:26:26:26:53 | locatio ... ring(1) |
113122
| tst.js:26:26:26:53 | locatio ... ring(1) |
114123
edges
124+
| NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
125+
| NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
126+
| NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
127+
| NoSQLCodeInjection.js:18:24:18:31 | req.body | NoSQLCodeInjection.js:18:24:18:37 | req.body.query |
128+
| NoSQLCodeInjection.js:19:36:19:43 | req.body | NoSQLCodeInjection.js:19:36:19:48 | req.body.name |
129+
| NoSQLCodeInjection.js:19:36:19:43 | req.body | NoSQLCodeInjection.js:19:36:19:48 | req.body.name |
130+
| NoSQLCodeInjection.js:19:36:19:48 | req.body.name | NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name |
131+
| NoSQLCodeInjection.js:19:36:19:48 | req.body.name | NoSQLCodeInjection.js:19:24:19:48 | "name = ... dy.name |
115132
| angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search |
116133
| angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search |
117134
| angularjs.js:10:22:10:29 | location | angularjs.js:10:22:10:36 | location.search |
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const express = require("express"),
2+
mongodb = require("mongodb"),
3+
bodyParser = require("body-parser");
4+
5+
const MongoClient = mongodb.MongoClient;
6+
7+
const app = express();
8+
9+
app.use(bodyParser.urlencoded({ extended: true }));
10+
11+
app.post("/documents/find", (req, res) => {
12+
const query = {};
13+
query.title = req.body.title;
14+
MongoClient.connect("mongodb://localhost:27017/test", (err, db) => {
15+
let doc = db.collection("doc");
16+
17+
doc.find(query); // NOT OK, but that is flagged by js/sql-injection
18+
doc.find({ $where: req.body.query }); // NOT OK
19+
doc.find({ $where: "name = " + req.body.name }); // NOT OK
20+
});
21+
});

0 commit comments

Comments
 (0)