refactor the NoSQL model to use API graphs

This commit is contained in:
Erik Krogh Kristensen
2020-09-29 15:43:52 +02:00
parent 0158e2ffef
commit abdbe92720
4 changed files with 226 additions and 281 deletions

View File

@@ -301,6 +301,9 @@ module API {
/** Gets a data-flow node that defines this entry point. */
abstract DataFlow::Node getARhs();
/** Gets a API-node for this entry point. */
API::Node getNode() { result = root().getASuccessor(this) }
}
/**

View File

@@ -13,106 +13,39 @@ module NoSQL {
}
/**
* Gets a reference to an object where the "$where" property has been assigned to`rhs`.
* Gets a value that has been assigned to the "$where" property of an object that flows to `queryArg`.
*/
DataFlow::SourceNode getADollarWherePropertyValueSource(DataFlow::TypeTracker t, DataFlow::Node rhs) {
t.start() and
rhs = result.getAPropertyWrite("$where").getRhs()
or
exists(DataFlow::TypeTracker t2 |
result = getADollarWherePropertyValueSource(t2, rhs).track(t2, t)
)
}
/**
* Gets the value of a `$where` property of an object that flows to `n`.
*/
private DataFlow::Node getADollarWherePropertyValue(DataFlow::Node n) {
getADollarWherePropertyValueSource(DataFlow::TypeTracker::end(), result).flowsTo(n)
private DataFlow::Node getADollarWhereProperty(API::Node queryArg) {
result = queryArg.getMember("$where").getARhs()
}
/**
* Provides classes modeling the MongoDB library.
*/
private module MongoDB {
/**
* Gets an import of MongoDB.
*/
DataFlow::ModuleImportNode mongodb() { result.getPath() = "mongodb" }
/**
* Gets an access to `mongodb.MongoClient`.
*/
private DataFlow::SourceNode getAMongoClient(DataFlow::TypeTracker t) {
t.start() and
(
result = mongodb().getAPropertyRead("MongoClient")
or
exists(DataFlow::ParameterNode p |
p = result and
p = getAMongoDbCallback().getParameter(1) and
not p.getName().toLowerCase() = "db" // mongodb v2 provides a `Db` here
)
)
private API::Node getAMongoClient() {
result = API::moduleImport("mongodb").getMember("MongoClient")
or
exists(DataFlow::TypeTracker t2 | result = getAMongoClient(t2).track(t2, t))
// slightly imprecise, is not supposed to have a result if the parameter name is "db" (that would be a mongodb v2 `Db`).
result = getAMongoDbCallback().getParameter(1)
}
/** Gets an api node that refers to a `connect` callback. */
private API::Node getAMongoDbCallback() {
result = getAMongoClient().getMember("connect").getLastParameter()
}
/**
* Gets an access to `mongodb.MongoClient`.
* Gets an API node that may refer to a MongoDB database connection.
*/
DataFlow::SourceNode getAMongoClient() { result = getAMongoClient(DataFlow::TypeTracker::end()) }
/** Gets a data flow node that leads to a `connect` callback. */
private DataFlow::SourceNode getAMongoDbCallback(DataFlow::TypeBackTracker t) {
t.start() and
result = getAMongoClient().getAMemberCall("connect").getLastArgument().getALocalSource()
private API::Node getAMongoDb() {
result = getAMongoClient().getMember("db").getReturn()
or
exists(DataFlow::TypeBackTracker t2 | result = getAMongoDbCallback(t2).backtrack(t2, t))
}
/** Gets a data flow node that leads to a `connect` callback. */
private DataFlow::FunctionNode getAMongoDbCallback() {
result = getAMongoDbCallback(DataFlow::TypeBackTracker::end())
}
/**
* Gets an expression that may refer to a MongoDB database connection.
*/
private DataFlow::SourceNode getAMongoDb(DataFlow::TypeTracker t) {
t.start() and
(
exists(DataFlow::ParameterNode p |
p = result and
p = getAMongoDbCallback().getParameter(1) and
not p.getName().toLowerCase() = "client" // mongodb v3 provides a `Mongoclient` here
)
or
result = getAMongoClient().getAMethodCall("db")
)
or
exists(DataFlow::TypeTracker t2 | result = getAMongoDb(t2).track(t2, t))
}
/**
* Gets an expression that may refer to a MongoDB database connection.
*/
DataFlow::SourceNode getAMongoDb() { result = getAMongoDb(DataFlow::TypeTracker::end()) }
/**
* A data flow node that may hold a MongoDB collection.
*/
abstract class Collection extends DataFlow::SourceNode { }
/**
* A collection resulting from calling `Db.collection(...)`.
*/
private class CollectionFromDb extends Collection {
CollectionFromDb() {
this = getAMongoDb().getAMethodCall("collection")
or
this = getAMongoDb().getAMethodCall("collection").getCallback(1).getParameter(0)
}
// slightly imprecise, is not supposed to have a result if the parameter name is "client" (that would be a mongodb v3 `Mongoclient`).
result = getAMongoDbCallback().getParameter(1)
}
/**
@@ -121,32 +54,55 @@ private module MongoDB {
* Note that this also covers `mongoose` models since they are subtypes
* of `mongodb.Collection`.
*/
private class CollectionFromType extends Collection {
CollectionFromType() { hasUnderlyingType("mongodb", "Collection") }
private class TypedMongoCollection extends API::EntryPoint {
TypedMongoCollection() { this = "TypedMongoCollection" }
override DataFlow::SourceNode getAUse() { result.hasUnderlyingType("mongodb", "Collection") }
override DataFlow::Node getARhs() { none() }
}
/** Gets a data flow node referring to a MongoDB collection. */
private DataFlow::SourceNode getACollection(DataFlow::TypeTracker t) {
t.start() and
result instanceof Collection
private API::Node getACollection() {
// A collection resulting from calling `Db.collection(...)`.
exists(API::Node collection | collection = getAMongoDb().getMember("collection").getReturn() |
result = collection
or
result = collection.getParameter(1).getParameter(0)
)
or
exists(DataFlow::TypeTracker t2 | result = getACollection(t2).track(t2, t))
result = any(TypedMongoCollection c).getNode()
}
/** Gets a data flow node referring to a MongoDB collection. */
DataFlow::SourceNode getACollection() { result = getACollection(DataFlow::TypeTracker::end()) }
/** A call to a MongoDB query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
private class QueryCall extends DatabaseAccess, DataFlow::CallNode {
int queryArgIdx;
API::Node callee;
QueryCall() {
exists(string m | this = getACollection().getAMethodCall(m) |
CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
)
exists(string method |
CollectionMethodSignatures::interpretsArgumentAsQuery(method, queryArgIdx) and
callee = getACollection().getMember(method)
) and
this = callee.getACall()
}
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
DataFlow::Node getACodeOperator() {
result = getADollarWhereProperty(callee.getParameter(queryArgIdx))
}
}
/**
* An expression that is interpreted as a MongoDB query.
*/
class Query extends NoSQL::Query {
QueryCall qc;
Query() { this = qc.getAQueryArgument().asExpr() }
override DataFlow::Node getACodeOperator() { result = qc.getACodeOperator() }
}
/**
@@ -206,17 +162,6 @@ private module MongoDB {
)
}
}
/**
* An expression that is interpreted as a MongoDB query.
*/
class Query extends NoSQL::Query {
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
override DataFlow::Node getACodeOperator() {
result = getADollarWherePropertyValue(this.flow())
}
}
}
/**
@@ -226,81 +171,84 @@ private module Mongoose {
/**
* Gets an import of Mongoose.
*/
DataFlow::ModuleImportNode getAMongooseInstance() { result.getPath() = "mongoose" }
API::Node getAMongooseInstance() { result = API::moduleImport("mongoose") }
/**
* Gets a call to `mongoose.createConnection`.
* Gets a reference to `mongoose.createConnection`.
*/
DataFlow::CallNode createConnection() {
result = getAMongooseInstance().getAMemberCall("createConnection")
}
API::Node createConnection() { result = getAMongooseInstance().getMember("createConnection") }
/**
* A Mongoose function invocation.
* A Mongoose function.
*/
private class InvokeNode extends DataFlow::InvokeNode {
private class MongooseFunction extends API::Node {
/**
* Holds if this invocation returns an object of type `Query`.
* Gets the API node for the result from this function (if the function returns a Query).
*/
abstract predicate returnsQuery();
abstract API::Node getQueryReturn();
/**
* Holds if this invocation returns a `Query` that evaluates to one or
* Holds if this function returns a `Query` that evaluates to one or
* more Documents (`asArray` is false if it evaluates to a single
* Document).
*/
abstract predicate returnsDocumentQuery(boolean asArray);
/**
* Holds if this invocation interprets `arg` as a query.
* Gets an argument that this function interprets as a query.
*/
abstract predicate interpretsArgumentAsQuery(DataFlow::Node arg);
abstract API::Node getQueryArgument();
}
/**
* Provides classes modeling the Mongoose Model class
*/
module Model {
private class ModelInvokeNode extends InvokeNode, DataFlow::MethodCallNode {
ModelInvokeNode() { this = ref().getAMethodCall() }
private class ModelFunction extends MongooseFunction {
string methodName;
override predicate returnsQuery() { MethodSignatures::returnsQuery(getMethodName()) }
ModelFunction() { this = getModelObject().getMember(methodName) }
override API::Node getQueryReturn() {
MethodSignatures::returnsQuery(methodName) and result = this.getReturn()
}
override predicate returnsDocumentQuery(boolean asArray) {
MethodSignatures::returnsDocumentQuery(getMethodName(), asArray)
MethodSignatures::returnsDocumentQuery(methodName, asArray)
}
override predicate interpretsArgumentAsQuery(DataFlow::Node arg) {
override API::Node getQueryArgument() {
exists(int n |
MethodSignatures::interpretsArgumentAsQuery(this.getMethodName(), n) and
arg = this.getArgument(n)
MethodSignatures::interpretsArgumentAsQuery(methodName, n) and
result = this.getParameter(n)
)
}
}
/**
* Gets a data flow node referring to a Mongoose Model object.
* A Mongoose collection based on the type `mongoose.Model`.
*/
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
(
result = getAMongooseInstance().getAMemberCall("model")
or
exists(DataFlow::SourceNode conn | conn = createConnection() |
result = conn.getAMemberCall("model") or
result = conn.getAPropertyRead("models").getAPropertyRead()
)
or
result.hasUnderlyingType("mongoose", "Model")
) and
t.start()
or
exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t))
private class TypedMongooseModel extends API::EntryPoint {
TypedMongooseModel() { this = "TypedMongooseModel" }
override DataFlow::SourceNode getAUse() { result.hasUnderlyingType("mongoose", "Model") }
override DataFlow::Node getARhs() { none() }
}
/**
* Gets a data flow node referring to a Mongoose model object.
* Gets a API node referring to a Mongoose Model object.
*/
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
private API::Node getModelObject() {
result = getAMongooseInstance().getMember("model").getReturn()
or
exists(API::Node conn | conn = createConnection().getReturn() |
result = conn.getMember("model").getReturn() or
result = conn.getMember("models").getAMember()
)
or
result = any(TypedMongooseModel c).getNode()
}
/**
* Provides signatures for the Model methods.
@@ -362,58 +310,62 @@ private module Mongoose {
* Provides classes modeling the Mongoose Query class
*/
module Query {
private class QueryInvokeNode extends InvokeNode, DataFlow::MethodCallNode {
QueryInvokeNode() { this = ref().getAMethodCall() }
private class QueryFunction extends MongooseFunction {
string methodName;
override predicate returnsQuery() { MethodSignatures::returnsQuery(getMethodName()) }
QueryFunction() { this = getAMongooseQuery().getMember(methodName) }
override predicate returnsDocumentQuery(boolean asArray) {
MethodSignatures::returnsDocumentQuery(getMethodName(), asArray)
override API::Node getQueryReturn() {
MethodSignatures::returnsQuery(methodName) and result = this.getReturn()
}
override predicate interpretsArgumentAsQuery(DataFlow::Node arg) {
override predicate returnsDocumentQuery(boolean asArray) {
MethodSignatures::returnsDocumentQuery(methodName, asArray)
}
override API::Node getQueryArgument() {
exists(int n |
MethodSignatures::interpretsArgumentAsQuery(this.getMethodName(), n) and
arg = this.getArgument(n)
MethodSignatures::interpretsArgumentAsQuery(methodName, n) and
result = this.getParameter(n)
)
}
}
private class NewQueryInvokeNode extends InvokeNode {
NewQueryInvokeNode() {
this = getAMongooseInstance().getAPropertyRead("Query").getAnInstantiation()
}
private class NewQueryFunction extends MongooseFunction {
NewQueryFunction() { this = getAMongooseInstance().getMember("Query") }
override predicate returnsQuery() { any() }
override API::Node getQueryReturn() { result = this.getInstance() }
override predicate returnsDocumentQuery(boolean asArray) { none() }
override predicate interpretsArgumentAsQuery(DataFlow::Node arg) { arg = this.getArgument(2) }
override API::Node getQueryArgument() { result = this.getParameter(2) }
}
/**
* A Mongoose query.
*/
private class TypedMongooseQuery extends API::EntryPoint {
TypedMongooseQuery() { this = "TypedMongooseQuery" }
override DataFlow::SourceNode getAUse() { result.hasUnderlyingType("mongoose", "Query") }
override DataFlow::Node getARhs() { none() }
}
/**
* Gets a data flow node referring to a Mongoose query object.
*/
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
(
result.(InvokeNode).returnsQuery() or
result.hasUnderlyingType("mongoose", "Query")
) and
t.start()
API::Node getAMongooseQuery() {
result = any(MongooseFunction f).getQueryReturn()
or
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode succ | succ = ref(t2) |
result = succ.track(t2, t)
or
result = succ.getAMethodCall(any(string name | MethodSignatures::returnsQuery(name))) and
t = t2.continue()
)
result = any(TypedMongooseQuery c).getNode()
or
result =
getAMongooseQuery()
.getMember(any(string name | MethodSignatures::returnsQuery(name)))
.getReturn()
}
/**
* Gets a data flow node referring to a Mongoose query object.
*/
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
/**
* Provides signatures for the Query methods.
*/
@@ -553,19 +505,23 @@ private module Mongoose {
* Provides classes modeling the Mongoose Document class
*/
module Document {
private class DocumentInvokeNode extends InvokeNode, DataFlow::MethodCallNode {
DocumentInvokeNode() { this = ref().getAMethodCall() }
private class DocumentFunction extends MongooseFunction {
string methodName;
override predicate returnsQuery() { MethodSignatures::returnsQuery(getMethodName()) }
DocumentFunction() { this = getAMongooseDocument().getMember(methodName) }
override predicate returnsDocumentQuery(boolean asArray) {
MethodSignatures::returnsDocumentQuery(getMethodName(), asArray)
override API::Node getQueryReturn() {
MethodSignatures::returnsQuery(methodName) and result = this.getReturn()
}
override predicate interpretsArgumentAsQuery(DataFlow::Node arg) {
override predicate returnsDocumentQuery(boolean asArray) {
MethodSignatures::returnsDocumentQuery(methodName, asArray)
}
override API::Node getQueryArgument() {
exists(int n |
MethodSignatures::interpretsArgumentAsQuery(this.getMethodName(), n) and
arg = this.getArgument(n)
MethodSignatures::interpretsArgumentAsQuery(methodName, n) and
result = this.getParameter(n)
)
}
}
@@ -573,67 +529,59 @@ private module Mongoose {
/**
* A Mongoose Document that is retrieved from the backing database.
*/
class RetrievedDocument extends DataFlow::SourceNode {
class RetrievedDocument extends API::Node {
RetrievedDocument() {
exists(boolean asArray, DataFlow::ParameterNode param |
exists(InvokeNode call |
call.returnsDocumentQuery(asArray) and
param = call.getCallback(call.getNumArgument() - 1).getParameter(1)
exists(boolean asArray, API::Node param |
exists(MongooseFunction func |
func.returnsDocumentQuery(asArray) and
param = func.getLastParameter().getParameter(1)
)
or
exists(
DataFlow::SourceNode base, DataFlow::MethodCallNode call, string executor,
int paramIndex
|
exists(API::Node f, string executor, int paramIndex |
executor = "then" and paramIndex = 0
or
executor = "exec" and paramIndex = 1
|
base = Query::ref() and
call = base.getAMethodCall(executor) and
param = call.getCallback(0).getParameter(paramIndex) and
f = Query::getAMongooseQuery().getMember(executor) and
param = f.getParameter(0).getParameter(paramIndex) and
exists(DataFlow::MethodCallNode pred |
// limitation: look at the previous method call
// limitation: look at the previous method call
Query::MethodSignatures::returnsDocumentQuery(pred.getMethodName(), asArray) and
pred.getAMethodCall() = call
pred.getAMethodCall() = f.getACall()
)
)
|
asArray = false and this = param
or
asArray = true and
exists(DataFlow::PropRead access |
// limitation: look for direct accesses
access = param.getAPropertyRead() and
not exists(access.getPropertyName()) and
this = access
)
// limitation: look for direct accesses
this = param.getUnknownMember()
)
}
}
/**
* Gets a data flow node referring to a Mongoose Document object.
* A Mongoose document.
*/
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
(
result instanceof RetrievedDocument or
result.hasUnderlyingType("mongoose", "Document")
) and
t.start()
or
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode succ | succ = ref(t2) |
result = succ.track(t2, t)
or
result = succ.getAMethodCall(any(string name | MethodSignatures::returnsDocument(name))) and
t = t2.continue()
)
private class TypedMongooseDocument extends API::EntryPoint {
TypedMongooseDocument() { this = "TypedMongooseDocument" }
override DataFlow::SourceNode getAUse() { result.hasUnderlyingType("mongoose", "Document") }
override DataFlow::Node getARhs() { none() }
}
/**
* Gets a data flow node referring to a Mongoose Document object.
*/
DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) }
private API::Node getAMongooseDocument() {
result instanceof RetrievedDocument or
result = any(TypedMongooseDocument c).getNode() or
result =
getAMongooseDocument()
.getMember(any(string name | MethodSignatures::returnsDocument(name)))
.getReturn()
}
private module MethodSignatures {
/**
@@ -691,7 +639,9 @@ private module Mongoose {
string kind;
Credentials() {
exists(string prop | this = createConnection().getOptionArgument(3, prop).asExpr() |
exists(string prop |
this = createConnection().getParameter(3).getMember(prop).getARhs().asExpr()
|
prop = "user" and kind = "user name"
or
prop = "pass" and kind = "password"
@@ -705,38 +655,38 @@ private module Mongoose {
* An expression that is interpreted as a (part of a) MongoDB query.
*/
class MongoDBQueryPart extends NoSQL::Query {
MongoDBQueryPart() { any(InvokeNode call).interpretsArgumentAsQuery(this.flow()) }
MongooseFunction f;
MongoDBQueryPart() { this = f.getQueryArgument().getARhs().asExpr() }
override DataFlow::Node getACodeOperator() {
result = getADollarWherePropertyValue(this.flow())
result = getADollarWhereProperty(f.getQueryArgument())
}
}
/**
* An evaluation of a MongoDB query.
*/
class ShorthandQueryEvaluation extends DatabaseAccess {
InvokeNode invk;
class ShorthandQueryEvaluation extends DatabaseAccess, DataFlow::InvokeNode {
MongooseFunction f;
ShorthandQueryEvaluation() {
this = invk and
this = f.getACall() and
// shorthand for execution: provide a callback
invk.returnsQuery() and
exists(invk.getCallback(invk.getNumArgument() - 1))
exists(f.getQueryReturn()) and
exists(this.getCallback(this.getNumArgument() - 1))
}
override DataFlow::Node getAQueryArgument() {
// NB: the complete information is not easily accessible for deeply chained calls
invk.interpretsArgumentAsQuery(result)
f.getQueryArgument().getARhs() = result
}
}
class ExplicitQueryEvaluation extends DatabaseAccess {
ExplicitQueryEvaluation() {
// explicit execution using a Query method call
exists(string executor | executor = "exec" or executor = "then" or executor = "catch" |
Query::ref().getAMethodCall(executor) = this
)
Query::getAMongooseQuery().getMember(["exec", "then", "catch"]).getACall() = this
}
override DataFlow::Node getAQueryArgument() {
@@ -750,26 +700,6 @@ private module Mongoose {
* Provides classes modeling the Minimongo library.
*/
private module Minimongo {
/**
* Gets an expression that may refer to a Minimongo database.
*/
private DataFlow::SourceNode getADb(DataFlow::TypeTracker t) {
t.start() and
// new (require('minimongo')[DBKINDNAME])()
result = DataFlow::moduleImport("minimongo").getAPropertyRead().getAnInvocation()
or
exists(DataFlow::TypeTracker t2 | result = getADb(t2).track(t2, t))
}
/** Gets a data flow node referring to a Minimongo collection. */
private DataFlow::SourceNode getACollection(DataFlow::TypeTracker t) {
t.start() and
// db[COLLECTIONNAME]
result = getADb(DataFlow::TypeTracker::end()).getAPropertyRead()
or
exists(DataFlow::TypeTracker t2 | result = getACollection(t2).track(t2, t))
}
/**
* Provides signatures for the Collection methods.
*/
@@ -786,25 +716,32 @@ private module Minimongo {
/** A call to a Minimongo query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
int queryArgIdx;
API::Node callee;
QueryCall() {
exists(string m | this = getACollection(DataFlow::TypeTracker::end()).getAMethodCall(m) |
exists(string m |
callee = API::moduleImport("minimongo").getAMember().getReturn().getAMember().getMember(m) and
this = callee.getACall() and
CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
)
}
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
DataFlow::Node getACodeOperator() {
result = getADollarWhereProperty(callee.getParameter(queryArgIdx))
}
}
/**
* An expression that is interpreted as a Minimongo query.
*/
class Query extends NoSQL::Query {
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
QueryCall qc;
override DataFlow::Node getACodeOperator() {
result = getADollarWherePropertyValue(this.flow())
}
Query() { this = qc.getAQueryArgument().asExpr() }
override DataFlow::Node getACodeOperator() { result = qc.getACodeOperator() }
}
}
@@ -812,49 +749,35 @@ private module Minimongo {
* Provides classes modeling the MarsDB library.
*/
private module MarsDB {
/**
* Gets an expression that may refer to a MarsDB database.
*/
private DataFlow::SourceNode getADb(DataFlow::TypeTracker t) {
t.start() and
// Collection = require('marsdb')
result = DataFlow::moduleImport("marsdb")
or
exists(DataFlow::TypeTracker t2 | result = getADb(t2).track(t2, t))
}
/** Gets a data flow node referring to a MarsDB collection. */
private DataFlow::SourceNode getACollection(DataFlow::TypeTracker t) {
t.start() and
// new Collection(...)
result =
getADb(DataFlow::TypeTracker::end()).getAPropertyRead("Collection").getAnInstantiation()
or
exists(DataFlow::TypeTracker t2 | result = getACollection(t2).track(t2, t))
}
/** A call to a MarsDB query method. */
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
int queryArgIdx;
API::Node callee;
QueryCall() {
exists(string m | this = getACollection(DataFlow::TypeTracker::end()).getAMethodCall(m) |
exists(string m |
callee = API::moduleImport("marsdb").getMember("Collection").getInstance().getMember(m) and
this = callee.getACall() and
// implements parts of the Minimongo interface
Minimongo::CollectionMethodSignatures::interpretsArgumentAsQuery(m, queryArgIdx)
)
}
override DataFlow::Node getAQueryArgument() { result = getArgument(queryArgIdx) }
DataFlow::Node getACodeOperator() {
result = getADollarWhereProperty(callee.getParameter(queryArgIdx))
}
}
/**
* An expression that is interpreted as a MarsDB query.
*/
class Query extends NoSQL::Query {
Query() { this = any(QueryCall qc).getAQueryArgument().asExpr() }
QueryCall qc;
override DataFlow::Node getACodeOperator() {
result = getADollarWherePropertyValue(this.flow())
}
Query() { this = qc.getAQueryArgument().asExpr() }
override DataFlow::Node getACodeOperator() { result = qc.getACodeOperator() }
}
}

View File

@@ -136,6 +136,8 @@ nodes
| mongoose.js:94:51:94:55 | query |
| mongoose.js:96:46:96:50 | query |
| mongoose.js:96:46:96:50 | query |
| mongoose.js:111:14:111:18 | query |
| mongoose.js:111:14:111:18 | query |
| mongooseJsonParse.js:19:11:19:20 | query |
| mongooseJsonParse.js:19:19:19:20 | {} |
| mongooseJsonParse.js:20:19:20:44 | JSON.pa ... y.data) |
@@ -337,6 +339,8 @@ edges
| mongoose.js:20:11:20:20 | query | mongoose.js:94:51:94:55 | query |
| mongoose.js:20:11:20:20 | query | mongoose.js:96:46:96:50 | query |
| mongoose.js:20:11:20:20 | query | mongoose.js:96:46:96:50 | query |
| mongoose.js:20:11:20:20 | query | mongoose.js:111:14:111:18 | query |
| mongoose.js:20:11:20:20 | query | mongoose.js:111:14:111:18 | query |
| mongoose.js:20:19:20:20 | {} | mongoose.js:20:11:20:20 | query |
| mongoose.js:21:19:21:26 | req.body | mongoose.js:21:19:21:32 | req.body.title |
| mongoose.js:21:19:21:26 | req.body | mongoose.js:21:19:21:32 | req.body.title |
@@ -403,6 +407,8 @@ edges
| mongoose.js:21:19:21:32 | req.body.title | mongoose.js:94:51:94:55 | query |
| mongoose.js:21:19:21:32 | req.body.title | mongoose.js:96:46:96:50 | query |
| mongoose.js:21:19:21:32 | req.body.title | mongoose.js:96:46:96:50 | query |
| mongoose.js:21:19:21:32 | req.body.title | mongoose.js:111:14:111:18 | query |
| mongoose.js:21:19:21:32 | req.body.title | mongoose.js:111:14:111:18 | query |
| mongoose.js:24:25:24:29 | query | mongoose.js:24:24:24:30 | [query] |
| mongoose.js:24:25:24:29 | query | mongoose.js:24:24:24:30 | [query] |
| mongooseJsonParse.js:19:11:19:20 | query | mongooseJsonParse.js:23:19:23:23 | query |
@@ -490,6 +496,7 @@ edges
| mongoose.js:92:46:92:50 | query | mongoose.js:21:19:21:26 | req.body | mongoose.js:92:46:92:50 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongoose.js:94:51:94:55 | query | mongoose.js:21:19:21:26 | req.body | mongoose.js:94:51:94:55 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongoose.js:96:46:96:50 | query | mongoose.js:21:19:21:26 | req.body | mongoose.js:96:46:96:50 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongoose.js:111:14:111:18 | query | mongoose.js:21:19:21:26 | req.body | mongoose.js:111:14:111:18 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongooseJsonParse.js:23:19:23:23 | query | mongooseJsonParse.js:20:30:20:43 | req.query.data | mongooseJsonParse.js:23:19:23:23 | query | This query depends on $@. | mongooseJsonParse.js:20:30:20:43 | req.query.data | a user-provided value |
| mongooseModelClient.js:11:16:11:24 | { id: v } | mongooseModelClient.js:10:22:10:29 | req.body | mongooseModelClient.js:11:16:11:24 | { id: v } | This query depends on $@. | mongooseModelClient.js:10:22:10:29 | req.body | a user-provided value |
| mongooseModelClient.js:12:16:12:34 | { id: req.body.id } | mongooseModelClient.js:12:22:12:29 | req.body | mongooseModelClient.js:12:16:12:34 | { id: req.body.id } | This query depends on $@. | mongooseModelClient.js:12:22:12:29 | req.body | a user-provided value |

View File

@@ -97,4 +97,16 @@ app.post('/documents/find', (req, res) => {
Document.find(X).then(Y, (err) => err.count(query)); // OK
Document.count(X, (err, res) => res.count(query)); // OK (res is a number)
function innocent(X, Y, query) { // To detect if API-graphs were used incorrectly.
return new Mongoose.Query("constant", "constant", "constant");
}
new innocent(X, Y, query);
function getQueryConstructor() {
return Mongoose.Query;
}
var C = getQueryConstructor();
new C(X, Y, query); // NOT OK
});