mirror of
https://github.com/github/codeql.git
synced 2026-05-02 20:25:13 +02:00
Merge pull request #14070 from yoff/python/promote-nosql-query
Python: promote nosql query
This commit is contained in:
@@ -378,6 +378,68 @@ module SqlExecution {
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a class for modeling NoSQL execution APIs. */
|
||||
module NoSqlExecution {
|
||||
/**
|
||||
* A data-flow node that executes NoSQL queries.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `NoSqlExecution` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the NoSQL query to be executed. */
|
||||
abstract DataFlow::Node getQuery();
|
||||
|
||||
/** Holds if this query will unpack/interpret a dictionary */
|
||||
abstract predicate interpretsDict();
|
||||
|
||||
/** Holds if this query can be dangerous when run on a user-controlled string */
|
||||
abstract predicate vulnerableToStrings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes NoSQL queries.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `NoSqlExecution::Range` instead.
|
||||
*/
|
||||
class NoSqlExecution extends DataFlow::Node instanceof NoSqlExecution::Range {
|
||||
/** Gets the argument that specifies the NoSQL query to be executed. */
|
||||
DataFlow::Node getQuery() { result = super.getQuery() }
|
||||
|
||||
/** Holds if this query will unpack/interpret a dictionary */
|
||||
predicate interpretsDict() { super.interpretsDict() }
|
||||
|
||||
/** Holds if this query can be dangerous when run on a user-controlled string */
|
||||
predicate vulnerableToStrings() { super.vulnerableToStrings() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling NoSql sanitization-related APIs. */
|
||||
module NoSqlSanitizer {
|
||||
/**
|
||||
* A data-flow node that collects functions sanitizing NoSQL queries.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `NoSQLSanitizer` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the NoSql query to be sanitized. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that collects functions sanitizing NoSQL queries.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `NoSQLSanitizer::Range` instead.
|
||||
*/
|
||||
class NoSqlSanitizer extends DataFlow::Node instanceof NoSqlSanitizer::Range {
|
||||
/** Gets the argument that specifies the NoSql query to be sanitized. */
|
||||
DataFlow::Node getAnInput() { result = super.getAnInput() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes a regular expression.
|
||||
*
|
||||
|
||||
@@ -10,6 +10,7 @@ private import semmle.python.frameworks.Aiomysql
|
||||
private import semmle.python.frameworks.Aiosqlite
|
||||
private import semmle.python.frameworks.Aiopg
|
||||
private import semmle.python.frameworks.Asyncpg
|
||||
private import semmle.python.frameworks.BSon
|
||||
private import semmle.python.frameworks.CassandraDriver
|
||||
private import semmle.python.frameworks.ClickhouseDriver
|
||||
private import semmle.python.frameworks.Cryptodome
|
||||
@@ -42,6 +43,7 @@ private import semmle.python.frameworks.Phoenixdb
|
||||
private import semmle.python.frameworks.Psycopg2
|
||||
private import semmle.python.frameworks.Pycurl
|
||||
private import semmle.python.frameworks.Pydantic
|
||||
private import semmle.python.frameworks.PyMongo
|
||||
private import semmle.python.frameworks.Pymssql
|
||||
private import semmle.python.frameworks.PyMySQL
|
||||
private import semmle.python.frameworks.Pyodbc
|
||||
|
||||
38
python/ql/lib/semmle/python/frameworks/BSon.qll
Normal file
38
python/ql/lib/semmle/python/frameworks/BSon.qll
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `bson` PyPI package.
|
||||
* See
|
||||
* - https://pypi.org/project/bson/
|
||||
* - https://github.com/py-bson/bson
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for the `bson` PyPI package.
|
||||
* See
|
||||
* - https://pypi.org/project/bson/
|
||||
* - https://github.com/py-bson/bson
|
||||
*/
|
||||
private module BSon {
|
||||
/**
|
||||
* ObjectId returns a string representing an id.
|
||||
* If at any time ObjectId can't parse it's input (like when a tainted dict in passed in),
|
||||
* then ObjectId will throw an error preventing the query from running.
|
||||
*/
|
||||
private class BsonObjectIdCall extends DataFlow::CallCfgNode, NoSqlSanitizer::Range {
|
||||
BsonObjectIdCall() {
|
||||
exists(API::Node mod |
|
||||
mod = API::moduleImport("bson")
|
||||
or
|
||||
mod = API::moduleImport("bson").getMember(["objectid", "json_util"])
|
||||
|
|
||||
this = mod.getMember("ObjectId").getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
}
|
||||
299
python/ql/lib/semmle/python/frameworks/PyMongo.qll
Normal file
299
python/ql/lib/semmle/python/frameworks/PyMongo.qll
Normal file
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the PyMongo bindings.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
private module PyMongo {
|
||||
// API Nodes returning `Mongo` instances.
|
||||
/** Gets a reference to `pymongo.MongoClient` */
|
||||
private API::Node pyMongo() {
|
||||
result = API::moduleImport("pymongo").getMember("MongoClient").getReturn()
|
||||
or
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient
|
||||
result =
|
||||
API::moduleImport("pymongo").getMember("mongo_client").getMember("MongoClient").getReturn()
|
||||
}
|
||||
|
||||
/** Gets a reference to `flask_pymongo.PyMongo` */
|
||||
private API::Node flask_PyMongo() {
|
||||
result = API::moduleImport("flask_pymongo").getMember("PyMongo").getReturn()
|
||||
}
|
||||
|
||||
/** Gets a reference to `mongoengine` */
|
||||
private API::Node mongoEngine() { result = API::moduleImport("mongoengine") }
|
||||
|
||||
/** Gets a reference to `flask_mongoengine.MongoEngine` */
|
||||
private API::Node flask_MongoEngine() {
|
||||
result = API::moduleImport("flask_mongoengine").getMember("MongoEngine").getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to an initialized `Mongo` instance.
|
||||
* See `pyMongo()`, `flask_PyMongo()`
|
||||
*/
|
||||
private API::Node mongoClientInstance() {
|
||||
result = pyMongo() or
|
||||
result = flask_PyMongo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a `Mongo` DB instance.
|
||||
*
|
||||
* ```py
|
||||
* from flask_pymongo import PyMongo
|
||||
* mongo = PyMongo(app)
|
||||
* mongo.db.user.find({'name': safe_search})
|
||||
* ```
|
||||
*
|
||||
* `mongo.db` would be a `Mongo` instance.
|
||||
*/
|
||||
private API::Node mongoDBInstance() {
|
||||
result = mongoClientInstance().getASubscript()
|
||||
or
|
||||
result = mongoClientInstance().getAMember()
|
||||
or
|
||||
result = mongoEngine().getMember(["get_db", "connect"]).getReturn()
|
||||
or
|
||||
result = mongoEngine().getMember("connection").getMember(["get_db", "connect"]).getReturn()
|
||||
or
|
||||
result = flask_MongoEngine().getMember("get_db").getReturn()
|
||||
or
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.get_default_database
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.get_database
|
||||
result = mongoClientInstance().getMember(["get_default_database", "get_database"]).getReturn()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a `Mongo` collection.
|
||||
*
|
||||
* ```py
|
||||
* from flask_pymongo import PyMongo
|
||||
* mongo = PyMongo(app)
|
||||
* mongo.db.user.find({'name': safe_search})
|
||||
* ```
|
||||
*
|
||||
* `mongo.db.user` would be a `Mongo` collection.
|
||||
*/
|
||||
private API::Node mongoCollection() {
|
||||
result = mongoDBInstance().getASubscript()
|
||||
or
|
||||
result = mongoDBInstance().getAMember()
|
||||
or
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/database.html#pymongo.database.Database.get_collection
|
||||
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/database.html#pymongo.database.Database.create_collection
|
||||
result = mongoDBInstance().getMember(["get_collection", "create_collection"]).getReturn()
|
||||
}
|
||||
|
||||
/** Gets the name of a find_* relevant `Mongo` collection-level operation method. */
|
||||
private string mongoCollectionMethodName() {
|
||||
result in [
|
||||
"find", "find_raw_batches", "find_one", "find_one_and_delete", "find_and_modify",
|
||||
"find_one_and_replace", "find_one_and_update", "find_one_or_404"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a `Mongo` collection method call
|
||||
*
|
||||
* ```py
|
||||
* from flask_pymongo import PyMongo
|
||||
* mongo = PyMongo(app)
|
||||
* mongo.db.user.find({'name': safe_search})
|
||||
* ```
|
||||
*
|
||||
* `mongo.db.user.find({'name': safe_search})` would be a collection method call.
|
||||
*/
|
||||
private class MongoCollectionCall extends API::CallNode, NoSqlExecution::Range {
|
||||
MongoCollectionCall() {
|
||||
this = mongoCollection().getMember(mongoCollectionMethodName()).getACall()
|
||||
}
|
||||
|
||||
/** Gets the argument that specifies the NoSQL query to be executed, as an API::node */
|
||||
pragma[inline]
|
||||
API::Node getQueryAsApiNode() {
|
||||
// 'filter' is allowed keyword in pymongo, see https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.find
|
||||
result = this.getParameter(0, "filter")
|
||||
}
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getQueryAsApiNode().asSink() }
|
||||
|
||||
override predicate interpretsDict() { any() }
|
||||
|
||||
override predicate vulnerableToStrings() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.aggregate
|
||||
*/
|
||||
private class MongoCollectionAggregation extends API::CallNode, NoSqlExecution::Range {
|
||||
MongoCollectionAggregation() { this = mongoCollection().getMember("aggregate").getACall() }
|
||||
|
||||
override DataFlow::Node getQuery() {
|
||||
result = this.getParameter(0, "pipeline").getASubscript().asSink()
|
||||
}
|
||||
|
||||
override predicate interpretsDict() { any() }
|
||||
|
||||
override predicate vulnerableToStrings() { none() }
|
||||
}
|
||||
|
||||
private class MongoMapReduce extends API::CallNode, NoSqlExecution::Range {
|
||||
MongoMapReduce() { this = mongoCollection().getMember("map_reduce").getACall() }
|
||||
|
||||
override DataFlow::Node getQuery() { result in [this.getArg(0), this.getArg(1)] }
|
||||
|
||||
override predicate interpretsDict() { none() }
|
||||
|
||||
override predicate vulnerableToStrings() { any() }
|
||||
}
|
||||
|
||||
private class MongoMapReduceQuery extends API::CallNode, NoSqlExecution::Range {
|
||||
MongoMapReduceQuery() { this = mongoCollection().getMember("map_reduce").getACall() }
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getArgByName("query") }
|
||||
|
||||
override predicate interpretsDict() { any() }
|
||||
|
||||
override predicate vulnerableToStrings() { none() }
|
||||
}
|
||||
|
||||
/** The `$where` query operator executes a string as JavaScript. */
|
||||
private class WhereQueryOperator extends DataFlow::Node, Decoding::Range {
|
||||
DataFlow::Node query;
|
||||
|
||||
WhereQueryOperator() {
|
||||
exists(API::Node dictionary |
|
||||
dictionary = any(MongoCollectionCall c).getQueryAsApiNode() and
|
||||
query = dictionary.getSubscript("$where").asSink() and
|
||||
this = dictionary.getAValueReachingSink()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = query }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getFormat() { result = "NoSQL" }
|
||||
|
||||
override predicate mayExecuteInput() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `$function` query operator executes its `body` string as JavaScript.
|
||||
*
|
||||
* See https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/#mongodb-expression-exp.-function
|
||||
*/
|
||||
private class FunctionQueryOperator extends DataFlow::Node, Decoding::Range {
|
||||
DataFlow::Node query;
|
||||
|
||||
FunctionQueryOperator() {
|
||||
exists(API::Node dictionary |
|
||||
dictionary =
|
||||
any(MongoCollectionCall c).getQueryAsApiNode().getASubscript*().getSubscript("$function") and
|
||||
query = dictionary.getSubscript("body").asSink() and
|
||||
this = dictionary.getAValueReachingSink()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = query }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getFormat() { result = "NoSQL" }
|
||||
|
||||
override predicate mayExecuteInput() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `$accumulator` query operator executes strings in some of its fields as JavaScript.
|
||||
*
|
||||
* See https://www.mongodb.com/docs/manual/reference/operator/aggregation/accumulator/#mongodb-group-grp.-accumulator
|
||||
*/
|
||||
private class AccumulatorQueryOperator extends DataFlow::Node, Decoding::Range {
|
||||
DataFlow::Node query;
|
||||
|
||||
AccumulatorQueryOperator() {
|
||||
exists(API::Node dictionary |
|
||||
dictionary =
|
||||
mongoCollection()
|
||||
.getMember("aggregate")
|
||||
.getACall()
|
||||
.getParameter(0)
|
||||
.getASubscript*()
|
||||
.getSubscript("$accumulator") and
|
||||
query = dictionary.getSubscript(["init", "accumulate", "merge", "finalize"]).asSink() and
|
||||
this = dictionary.getAValueReachingSink()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = query }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getFormat() { result = "NoSQL" }
|
||||
|
||||
override predicate mayExecuteInput() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a call from a class whose base is a reference to `mongoEngine()` or `flask_MongoEngine()`'s
|
||||
* `Document` or `EmbeddedDocument` objects and its attribute is `objects`.
|
||||
*
|
||||
* ```py
|
||||
* from flask_mongoengine import MongoEngine
|
||||
* db = MongoEngine(app)
|
||||
* class Movie(db.Document):
|
||||
* title = db.StringField(required=True)
|
||||
*
|
||||
* Movie.objects(__raw__=json_search)
|
||||
* ```
|
||||
*
|
||||
* `Movie.objects(__raw__=json_search)` would be the result.
|
||||
*/
|
||||
private class MongoEngineObjectsCall extends DataFlow::CallCfgNode, NoSqlExecution::Range {
|
||||
MongoEngineObjectsCall() {
|
||||
this =
|
||||
[mongoEngine(), flask_MongoEngine()]
|
||||
.getMember(["Document", "EmbeddedDocument"])
|
||||
.getASubclass()
|
||||
.getMember("objects")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getArgByName(_) }
|
||||
|
||||
override predicate interpretsDict() { any() }
|
||||
|
||||
override predicate vulnerableToStrings() { none() }
|
||||
}
|
||||
|
||||
/** Gets a reference to `mongosanitizer.sanitizer.sanitize` */
|
||||
private class MongoSanitizerCall extends DataFlow::CallCfgNode, NoSqlSanitizer::Range {
|
||||
MongoSanitizerCall() {
|
||||
this =
|
||||
API::moduleImport("mongosanitizer").getMember("sanitizer").getMember("sanitize").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An equality operator can protect against dictionary interpretation.
|
||||
* For instance, in `{'password': {"$eq": password} }`, if a dictionary is injected into
|
||||
* `password`, it will not match.
|
||||
*/
|
||||
private class EqualityOperator extends DataFlow::Node, NoSqlSanitizer::Range {
|
||||
EqualityOperator() {
|
||||
this =
|
||||
any(MongoCollectionCall c).getQueryAsApiNode().getASubscript*().getSubscript("$eq").asSink()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "NoSql injection"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "NoSql injection"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module NoSqlInjection {
|
||||
private newtype TFlowState =
|
||||
TString() or
|
||||
TDict()
|
||||
|
||||
/** A flow state, tracking the structure of the data. */
|
||||
abstract class FlowState extends TFlowState {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
/** A state where the tracked data is only a string. */
|
||||
class String extends FlowState, TString {
|
||||
override string toString() { result = "String" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A state where the tracked data has been converted to
|
||||
* a dictionary.
|
||||
*
|
||||
* We include cases where data represent JSON objects, so
|
||||
* it could actually still be just a string. It could
|
||||
* also contain query operators, or even JavaScript code.
|
||||
*/
|
||||
class Dict extends FlowState, TDict {
|
||||
override string toString() { result = "Dict" }
|
||||
}
|
||||
|
||||
/** A source allowing string inputs. */
|
||||
abstract class StringSource extends DataFlow::Node { }
|
||||
|
||||
/** A source of allowing dictionaries. */
|
||||
abstract class DictSource extends DataFlow::Node { }
|
||||
|
||||
/** A sink vulnerable to user controlled strings. */
|
||||
abstract class StringSink extends DataFlow::Node { }
|
||||
|
||||
/** A sink vulnerable to user controlled dictionaries. */
|
||||
abstract class DictSink extends DataFlow::Node { }
|
||||
|
||||
/** A data flow node where a string is converted into a dictionary. */
|
||||
abstract class StringToDictConversion extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the string to be converted. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
|
||||
/** Gets the resulting dictionary. */
|
||||
abstract DataFlow::Node getOutput();
|
||||
}
|
||||
|
||||
/** A remote flow source considered a source of user controlled strings. */
|
||||
class RemoteFlowSourceAsStringSource extends RemoteFlowSource, StringSource { }
|
||||
|
||||
/** A NoSQL query that is vulnerable to user controlled strings. */
|
||||
class NoSqlExecutionAsStringSink extends StringSink {
|
||||
NoSqlExecutionAsStringSink() {
|
||||
exists(NoSqlExecution noSqlExecution | this = noSqlExecution.getQuery() |
|
||||
noSqlExecution.vulnerableToStrings()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A NoSQL query that is vulnerable to user controlled dictionaries. */
|
||||
class NoSqlExecutionAsDictSink extends DictSink {
|
||||
NoSqlExecutionAsDictSink() {
|
||||
exists(NoSqlExecution noSqlExecution | this = noSqlExecution.getQuery() |
|
||||
noSqlExecution.interpretsDict()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A JSON decoding converts a string to a dictionary. */
|
||||
class JsonDecoding extends Decoding, StringToDictConversion {
|
||||
JsonDecoding() { this.getFormat() = "JSON" }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = Decoding.super.getAnInput() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = Decoding.super.getOutput() }
|
||||
}
|
||||
|
||||
/** A NoSQL decoding interprets a string as a dictionary. */
|
||||
class NoSqlDecoding extends Decoding, StringToDictConversion {
|
||||
NoSqlDecoding() { this.getFormat() = "NoSQL" }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = Decoding.super.getAnInput() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = Decoding.super.getOutput() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting NoSQL injection vulnerabilities
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.Concepts
|
||||
private import NoSqlInjectionCustomizations::NoSqlInjection as C
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting NoSQL injection vulnerabilities.
|
||||
*/
|
||||
module NoSqlInjectionConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = C::FlowState;
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
source instanceof C::StringSource and
|
||||
state instanceof C::String
|
||||
or
|
||||
source instanceof C::DictSource and
|
||||
state instanceof C::Dict
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
sink instanceof C::StringSink and
|
||||
(
|
||||
state instanceof C::String
|
||||
or
|
||||
// since Dicts can include strings,
|
||||
// e.g. JSON objects can encode strings.
|
||||
state instanceof C::Dict
|
||||
)
|
||||
or
|
||||
sink instanceof C::DictSink and
|
||||
state instanceof C::Dict
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node, FlowState state) {
|
||||
// Block `String` paths here, since they change state to `Dict`
|
||||
exists(C::StringToDictConversion c | node = c.getOutput()) and
|
||||
state instanceof C::String
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
|
||||
) {
|
||||
exists(C::StringToDictConversion c |
|
||||
nodeFrom = c.getAnInput() and
|
||||
nodeTo = c.getOutput()
|
||||
) and
|
||||
stateFrom instanceof C::String and
|
||||
stateTo instanceof C::Dict
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node = any(NoSqlSanitizer noSqlSanitizer).getAnInput()
|
||||
}
|
||||
}
|
||||
|
||||
module NoSqlInjectionFlow = TaintTracking::GlobalWithState<NoSqlInjectionConfig>;
|
||||
Reference in New Issue
Block a user