Skip to content

Commit 20cf044

Browse files
committed
JS: model marsdb and minimongo
1 parent f5e491c commit 20cf044

8 files changed

Lines changed: 214 additions & 0 deletions

File tree

change-notes/1.25/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- [fstream](https://www.npmjs.com/package/fstream)
77
- [jGrowl](https://github.com/stanlemon/jGrowl)
88
- [jQuery](https://jquery.com/)
9+
- [marsdb](https://www.npmjs.com/package/marsdb)
10+
- [minimongo](https://www.npmjs.com/package/minimongo/)
911

1012
## New queries
1113

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,3 +713,101 @@ private module Mongoose {
713713
}
714714
}
715715
}
716+
717+
/**
718+
* Provides classes modeling the Minimongo library.
719+
*/
720+
private module Minimongo {
721+
/**
722+
* Gets an expression that may refer to a Minimongo database.
723+
*/
724+
private DataFlow::SourceNode getADb(DataFlow::TypeTracker t) {
725+
t.start() and
726+
// new (require('minimongo')[DBKINDNAME])()
727+
result = DataFlow::moduleImport("minimongo").getAPropertyRead().getAnInvocation()
728+
or
729+
exists(DataFlow::TypeTracker t2 | result = getADb(t2).track(t2, t))
730+
}
731+
732+
/** Gets a data flow node referring to a Minimongo collection. */
733+
private DataFlow::SourceNode getACollection(DataFlow::TypeTracker t) {
734+
t.start() and
735+
// db[COLLECTIONNAME]
736+
result = getADb(DataFlow::TypeTracker::end()).getAPropertyRead()
737+
or
738+
exists(DataFlow::TypeTracker t2 | result = getACollection(t2).track(t2, t))
739+
}
740+
741+
module CollectionMethodSignatures {
742+
predicate interpretsArgumentAsQuery(string m, int queryArgIdx) {
743+
// implements most of the MongoDB interface
744+
MongoDB::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
745+
}
746+
}
747+
748+
/** A call to a Minimongo query method. */
749+
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
750+
int queryArgIdx;
751+
752+
QueryCall() {
753+
exists(string m | this = getACollection(DataFlow::TypeTracker::end()).getAMethodCall(m) |
754+
CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
755+
)
756+
}
757+
758+
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
759+
}
760+
761+
/**
762+
* An expression that is interpreted as a Minimongo query.
763+
*/
764+
class Query extends NoSQL::Query {
765+
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
766+
}
767+
}
768+
769+
/**
770+
* Provides classes modeling the MarsDB library.
771+
*/
772+
private module MarsDB {
773+
/**
774+
* Gets an expression that may refer to a MarsDB database.
775+
*/
776+
private DataFlow::SourceNode getADb(DataFlow::TypeTracker t) {
777+
t.start() and
778+
// Collection = require('marsdb')
779+
result = DataFlow::moduleImport("marsdb")
780+
or
781+
exists(DataFlow::TypeTracker t2 | result = getADb(t2).track(t2, t))
782+
}
783+
784+
/** Gets a data flow node referring to a MarsDB collection. */
785+
private DataFlow::SourceNode getACollection(DataFlow::TypeTracker t) {
786+
t.start() and
787+
// new Collection(...)
788+
result = getADb(DataFlow::TypeTracker::end()).getAPropertyRead("Collection").getAnInvocation()
789+
or
790+
exists(DataFlow::TypeTracker t2 | result = getACollection(t2).track(t2, t))
791+
}
792+
793+
/** A call to a MarsDB query method. */
794+
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
795+
int queryArgIdx;
796+
797+
QueryCall() {
798+
exists(string m | this = getACollection(DataFlow::TypeTracker::end()).getAMethodCall(m) |
799+
// implements parts of the Minimongo interface
800+
Minimongo::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
801+
)
802+
}
803+
804+
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
805+
}
806+
807+
/**
808+
* An expression that is interpreted as a MarsDB query.
809+
*/
810+
class Query extends NoSQL::Query {
811+
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
812+
}
813+
}

javascript/ql/test/query-tests/Security/CWE-089/untyped/DatabaseAccesses.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
| marsdb-flow-to.js:14:3:14:22 | db.myDoc.find(query) |
2+
| marsdb.js:16:3:16:17 | doc.find(query) |
3+
| minimongo.js:18:3:18:17 | doc.find(query) |
14
| mongodb.js:18:7:18:21 | doc.find(query) |
25
| mongodb.js:21:7:21:48 | doc.fin ... itle }) |
36
| mongodb.js:24:7:24:53 | doc.fin ... r(1) }) |

javascript/ql/test/query-tests/Security/CWE-089/untyped/SqlInjection.expected

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
11
nodes
2+
| marsdb-flow-to.js:10:9:10:18 | query |
3+
| marsdb-flow-to.js:10:17:10:18 | {} |
4+
| marsdb-flow-to.js:11:17:11:24 | req.body |
5+
| marsdb-flow-to.js:11:17:11:24 | req.body |
6+
| marsdb-flow-to.js:11:17:11:30 | req.body.title |
7+
| marsdb-flow-to.js:14:17:14:21 | query |
8+
| marsdb-flow-to.js:14:17:14:21 | query |
9+
| marsdb.js:12:9:12:18 | query |
10+
| marsdb.js:12:17:12:18 | {} |
11+
| marsdb.js:13:17:13:24 | req.body |
12+
| marsdb.js:13:17:13:24 | req.body |
13+
| marsdb.js:13:17:13:30 | req.body.title |
14+
| marsdb.js:16:12:16:16 | query |
15+
| marsdb.js:16:12:16:16 | query |
16+
| minimongo.js:14:9:14:18 | query |
17+
| minimongo.js:14:17:14:18 | {} |
18+
| minimongo.js:15:17:15:24 | req.body |
19+
| minimongo.js:15:17:15:24 | req.body |
20+
| minimongo.js:15:17:15:30 | req.body.title |
21+
| minimongo.js:18:12:18:16 | query |
22+
| minimongo.js:18:12:18:16 | query |
223
| mongodb.js:12:11:12:20 | query |
324
| mongodb.js:12:19:12:20 | {} |
425
| mongodb.js:13:19:13:26 | req.body |
@@ -150,6 +171,33 @@ nodes
150171
| tst.js:10:46:10:58 | req.params.id |
151172
| tst.js:10:46:10:58 | req.params.id |
152173
edges
174+
| marsdb-flow-to.js:10:9:10:18 | query | marsdb-flow-to.js:14:17:14:21 | query |
175+
| marsdb-flow-to.js:10:9:10:18 | query | marsdb-flow-to.js:14:17:14:21 | query |
176+
| marsdb-flow-to.js:10:17:10:18 | {} | marsdb-flow-to.js:10:9:10:18 | query |
177+
| marsdb-flow-to.js:11:17:11:24 | req.body | marsdb-flow-to.js:11:17:11:30 | req.body.title |
178+
| marsdb-flow-to.js:11:17:11:24 | req.body | marsdb-flow-to.js:11:17:11:30 | req.body.title |
179+
| marsdb-flow-to.js:11:17:11:30 | req.body.title | marsdb-flow-to.js:10:9:10:18 | query |
180+
| marsdb-flow-to.js:11:17:11:30 | req.body.title | marsdb-flow-to.js:10:17:10:18 | {} |
181+
| marsdb-flow-to.js:11:17:11:30 | req.body.title | marsdb-flow-to.js:14:17:14:21 | query |
182+
| marsdb-flow-to.js:11:17:11:30 | req.body.title | marsdb-flow-to.js:14:17:14:21 | query |
183+
| marsdb.js:12:9:12:18 | query | marsdb.js:16:12:16:16 | query |
184+
| marsdb.js:12:9:12:18 | query | marsdb.js:16:12:16:16 | query |
185+
| marsdb.js:12:17:12:18 | {} | marsdb.js:12:9:12:18 | query |
186+
| marsdb.js:13:17:13:24 | req.body | marsdb.js:13:17:13:30 | req.body.title |
187+
| marsdb.js:13:17:13:24 | req.body | marsdb.js:13:17:13:30 | req.body.title |
188+
| marsdb.js:13:17:13:30 | req.body.title | marsdb.js:12:9:12:18 | query |
189+
| marsdb.js:13:17:13:30 | req.body.title | marsdb.js:12:17:12:18 | {} |
190+
| marsdb.js:13:17:13:30 | req.body.title | marsdb.js:16:12:16:16 | query |
191+
| marsdb.js:13:17:13:30 | req.body.title | marsdb.js:16:12:16:16 | query |
192+
| minimongo.js:14:9:14:18 | query | minimongo.js:18:12:18:16 | query |
193+
| minimongo.js:14:9:14:18 | query | minimongo.js:18:12:18:16 | query |
194+
| minimongo.js:14:17:14:18 | {} | minimongo.js:14:9:14:18 | query |
195+
| minimongo.js:15:17:15:24 | req.body | minimongo.js:15:17:15:30 | req.body.title |
196+
| minimongo.js:15:17:15:24 | req.body | minimongo.js:15:17:15:30 | req.body.title |
197+
| minimongo.js:15:17:15:30 | req.body.title | minimongo.js:14:9:14:18 | query |
198+
| minimongo.js:15:17:15:30 | req.body.title | minimongo.js:14:17:14:18 | {} |
199+
| minimongo.js:15:17:15:30 | req.body.title | minimongo.js:18:12:18:16 | query |
200+
| minimongo.js:15:17:15:30 | req.body.title | minimongo.js:18:12:18:16 | query |
153201
| mongodb.js:12:11:12:20 | query | mongodb.js:18:16:18:20 | query |
154202
| mongodb.js:12:11:12:20 | query | mongodb.js:18:16:18:20 | query |
155203
| mongodb.js:12:19:12:20 | {} | mongodb.js:12:11:12:20 | query |
@@ -371,6 +419,9 @@ edges
371419
| tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' |
372420
| tst.js:10:46:10:58 | req.params.id | tst.js:10:10:10:64 | 'SELECT ... d + '"' |
373421
#select
422+
| marsdb-flow-to.js:14:17:14:21 | query | marsdb-flow-to.js:11:17:11:24 | req.body | marsdb-flow-to.js:14:17:14:21 | query | This query depends on $@. | marsdb-flow-to.js:11:17:11:24 | req.body | a user-provided value |
423+
| marsdb.js:16:12:16:16 | query | marsdb.js:13:17:13:24 | req.body | marsdb.js:16:12:16:16 | query | This query depends on $@. | marsdb.js:13:17:13:24 | req.body | a user-provided value |
424+
| minimongo.js:18:12:18:16 | query | minimongo.js:15:17:15:24 | req.body | minimongo.js:18:12:18:16 | query | This query depends on $@. | minimongo.js:15:17:15:24 | req.body | a user-provided value |
374425
| mongodb.js:18:16:18:20 | query | mongodb.js:13:19:13:26 | req.body | mongodb.js:18:16:18:20 | query | This query depends on $@. | mongodb.js:13:19:13:26 | req.body | a user-provided value |
375426
| mongodb.js:32:18:32:45 | { title ... itle) } | mongodb.js:26:19:26:26 | req.body | mongodb.js:32:18:32:45 | { title ... itle) } | This query depends on $@. | mongodb.js:26:19:26:26 | req.body | a user-provided value |
376427
| mongodb.js:54:16:54:20 | query | mongodb.js:49:19:49:33 | req.query.title | mongodb.js:54:16:54:20 | query | This query depends on $@. | mongodb.js:49:19:49:33 | req.query.title | a user-provided value |
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const MarsDB = require("marsdb");
2+
3+
const myDoc = new MarsDB.Collection("myDoc");
4+
5+
const db = {
6+
myDoc
7+
};
8+
9+
module.exports = db;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const express = require("express"),
2+
bodyParser = require("body-parser"),
3+
db = require('./marsdb-flow-from');
4+
5+
const app = express();
6+
7+
app.use(bodyParser.urlencoded({ extended: true }));
8+
9+
app.post("/documents/find", (req, res) => {
10+
const query = {};
11+
query.title = req.body.title;
12+
13+
// NOT OK: query is tainted by user-provided object value
14+
db.myDoc.find(query);
15+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const express = require("express"),
2+
MarsDB = require("marsdb"),
3+
bodyParser = require("body-parser");
4+
5+
let doc = new MarsDB.Collection("myDoc");
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+
15+
// NOT OK: query is tainted by user-provided object value
16+
doc.find(query);
17+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const express = require("express"),
2+
minimongo = require("minimongo"),
3+
bodyParser = require("body-parser");
4+
5+
var LocalDb = minimongo.MemoryDb,
6+
db = new LocalDb(),
7+
doc = db.myDocs;
8+
9+
const app = express();
10+
11+
app.use(bodyParser.urlencoded({ extended: true }));
12+
13+
app.post("/documents/find", (req, res) => {
14+
const query = {};
15+
query.title = req.body.title;
16+
17+
// NOT OK: query is tainted by user-provided object value
18+
doc.find(query);
19+
});

0 commit comments

Comments
 (0)