mirror of
https://github.com/github/codeql.git
synced 2026-02-11 20:51:06 +01:00
python: add machinery for MaD barriers
and reinstate previously removed barrier now as a MaD row
This commit is contained in:
@@ -36,6 +36,36 @@ module MakeBarrierGuard<BarrierGuardSig BaseGuard> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to barrier guards defined via models-as-data.
|
||||
*/
|
||||
module ExternalBarrierGuard {
|
||||
private predicate guardCheck(DataFlow::Node g, Expr e, boolean branch, string kind) {
|
||||
exists(API::CallNode call, API::Node parameter |
|
||||
parameter = call.getAParameter() and
|
||||
parameter = ModelOutput::getABarrierGuardNode(kind, branch)
|
||||
|
|
||||
g = call and
|
||||
e = parameter.asSink().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
private class BarrierGuard extends DataFlow::Node {
|
||||
BarrierGuard() { guardCheck(this, _, _, _) }
|
||||
|
||||
predicate blocksExpr(boolean outcome, Expr e, string kind) {
|
||||
guardCheck(this, e, outcome, kind)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a barrier guard node of the given `kind` defined via models-as-data.
|
||||
*/
|
||||
DataFlow::Node getAnExternalBarrierNode(string kind) {
|
||||
result = MakeStateBarrierGuard<string, BarrierGuard>::getABarrierNode(kind)
|
||||
}
|
||||
}
|
||||
|
||||
deprecated private module DeprecationWrapper {
|
||||
signature class LabeledBarrierGuardSig extends DataFlow::Node {
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,7 @@ module CredentialsExpr {
|
||||
private class CredentialsFromModel extends CredentialsNode {
|
||||
string kind;
|
||||
|
||||
CredentialsFromModel() { this = ModelOutput::getASinkNode("credentials-" + kind).asSink() }
|
||||
CredentialsFromModel() { ModelOutput::sinkNode(this, "credentials-" + kind) }
|
||||
|
||||
override string getCredentialsKind() { result = CredentialsExpr::normalizeKind(kind) }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ module NoSql {
|
||||
}
|
||||
|
||||
private class QueryFromModel extends Query {
|
||||
QueryFromModel() { this = ModelOutput::getASinkNode("nosql-injection").asSink() }
|
||||
QueryFromModel() { ModelOutput::sinkNode(this, "nosql-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ private module MongoDB {
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = [this.getAnArgument(), this.getOptionArgument(_, _)] and
|
||||
result = ModelOutput::getASinkNode("mongodb.sink").asSink()
|
||||
ModelOutput::sinkNode(result, "mongodb.sink")
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResult() {
|
||||
|
||||
@@ -9,7 +9,7 @@ module SQL {
|
||||
abstract class SqlString extends DataFlow::Node { }
|
||||
|
||||
private class SqlStringFromModel extends SqlString {
|
||||
SqlStringFromModel() { this = ModelOutput::getASinkNode("sql-injection").asSink() }
|
||||
SqlStringFromModel() { ModelOutput::sinkNode(this, "sql-injection") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ import Shared::ModelOutput as ModelOutput
|
||||
* A remote flow source originating from a MaD source row.
|
||||
*/
|
||||
private class RemoteFlowSourceFromMaD extends RemoteFlowSource {
|
||||
RemoteFlowSourceFromMaD() { this = ModelOutput::getASourceNode("remote").asSource() }
|
||||
RemoteFlowSourceFromMaD() { ModelOutput::sourceNode(this, "remote") }
|
||||
|
||||
override string getSourceType() { result = "Remote flow" }
|
||||
}
|
||||
@@ -39,9 +39,9 @@ private class RemoteFlowSourceFromMaD extends RemoteFlowSource {
|
||||
* A threat-model flow source originating from a data extension.
|
||||
*/
|
||||
private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Range {
|
||||
ThreatModelSourceFromDataExtension() { this = ModelOutput::getASourceNode(_).asSource() }
|
||||
ThreatModelSourceFromDataExtension() { ModelOutput::sourceNode(this, _) }
|
||||
|
||||
override string getThreatModel() { this = ModelOutput::getASourceNode(result).asSource() }
|
||||
override string getThreatModel() { ModelOutput::sourceNode(this, result) }
|
||||
|
||||
override string getSourceType() {
|
||||
result = "Source node (" + this.getThreatModel() + ") [from data-extension]"
|
||||
|
||||
@@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a barrier model exists for the given parameters. */
|
||||
private predicate barrierModel(string type, string path, string kind, string model) {
|
||||
// No deprecation adapter for barrier models, they were not around back then.
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::barrierModel(type, path, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a barrier guard model exists for the given parameters. */
|
||||
private predicate barrierGuardModel(
|
||||
string type, string path, string branch, string kind, string model
|
||||
) {
|
||||
// No deprecation adapter for barrier models, they were not around back then.
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::barrierGuardModel(type, path, branch, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a summary model `row` exists for the given parameters. */
|
||||
private predicate summaryModel(
|
||||
string type, string path, string input, string output, string kind, string model
|
||||
@@ -400,6 +420,8 @@ predicate isRelevantType(string type) {
|
||||
(
|
||||
sourceModel(type, _, _, _) or
|
||||
sinkModel(type, _, _, _) or
|
||||
barrierModel(type, _, _, _) or
|
||||
barrierGuardModel(type, _, _, _, _) or
|
||||
summaryModel(type, _, _, _, _, _) or
|
||||
typeModel(_, type, _)
|
||||
) and
|
||||
@@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) {
|
||||
(
|
||||
sourceModel(type, path, _, _) or
|
||||
sinkModel(type, path, _, _) or
|
||||
barrierModel(type, path, _, _) or
|
||||
barrierGuardModel(type, path, _, _, _) or
|
||||
summaryModel(type, path, _, _, _, _) or
|
||||
typeModel(_, type, path)
|
||||
)
|
||||
@@ -747,6 +771,32 @@ module ModelOutput {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a barrier model contributed `barrier` with the given `kind`.
|
||||
*/
|
||||
cached
|
||||
API::Node getABarrierNode(string kind, string model) {
|
||||
exists(string type, string path |
|
||||
barrierModel(type, path, kind, model) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a barrier model contributed `barrier` with the given `kind` for the given `branch`.
|
||||
*/
|
||||
cached
|
||||
API::Node getABarrierGuardNode(string kind, boolean branch, string model) {
|
||||
exists(string type, string path, string branch_str |
|
||||
branch = true and branch_str = "true"
|
||||
or
|
||||
branch = false and branch_str = "false"
|
||||
|
|
||||
barrierGuardModel(type, path, branch_str, kind, model) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a relevant summary exists for these parameters.
|
||||
*/
|
||||
@@ -789,15 +839,46 @@ module ModelOutput {
|
||||
private import codeql.mad.ModelValidation as SharedModelVal
|
||||
|
||||
/**
|
||||
* Holds if a CSV source model contributed `source` with the given `kind`.
|
||||
* Holds if an external model contributed `source` with the given `kind`.
|
||||
*/
|
||||
API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if a CSV sink model contributed `sink` with the given `kind`.
|
||||
* Holds if an external model contributed `sink` with the given `kind`.
|
||||
*/
|
||||
API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if an external model contributed `barrier` with the given `kind`.
|
||||
*/
|
||||
API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`.
|
||||
*/
|
||||
API::Node getABarrierGuardNode(string kind, boolean branch) {
|
||||
result = getABarrierGuardNode(kind, branch, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a source with the given kind in an external model.
|
||||
*/
|
||||
predicate sourceNode(DataFlow::Node node, string kind) { node = getASourceNode(kind).asSource() }
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a sink with the given kind in an external model.
|
||||
*/
|
||||
predicate sinkNode(DataFlow::Node node, string kind) { node = getASinkNode(kind).asSink() }
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a barrier with the given kind in an external model.
|
||||
*/
|
||||
predicate barrierNode(DataFlow::Node node, string kind) {
|
||||
node = getABarrierNode(kind).asSource()
|
||||
or
|
||||
node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind)
|
||||
}
|
||||
|
||||
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
|
||||
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }
|
||||
|
||||
|
||||
@@ -20,6 +20,26 @@ extensible predicate sourceModel(
|
||||
*/
|
||||
extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId);
|
||||
|
||||
/**
|
||||
* Holds if the value at `(type, path)` should be seen as a barrier
|
||||
* of the given `kind` and `madId` is the data extension row number.
|
||||
*/
|
||||
extensible predicate barrierModel(
|
||||
string type, string path, string kind, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if the value at `(type, path)` should be seen as a barrier guard
|
||||
* of the given `kind` and `madId` is the data extension row number.
|
||||
* `path` is assumed to lead to a parameter of a call (possibly `self`), and
|
||||
* the call is guarding the parameter.
|
||||
* `branch` is either `true` or `false`, indicating which branch of the guard
|
||||
* is protecting the parameter.
|
||||
*/
|
||||
extensible predicate barrierGuardModel(
|
||||
string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if in calls to `(type, path)`, the value referred to by `input`
|
||||
* can flow to the value referred to by `output` and `madId` is the data
|
||||
|
||||
@@ -16,6 +16,16 @@ extensions:
|
||||
extensible: summaryModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: barrierModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: barrierGuardModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: neutralModel
|
||||
|
||||
@@ -66,7 +66,7 @@ module CorsPermissiveConfiguration {
|
||||
* The value of cors origin when initializing the application.
|
||||
*/
|
||||
class CorsOriginSink extends Sink, DataFlow::ValueNode {
|
||||
CorsOriginSink() { this = ModelOutput::getASinkNode("cors-origin").asSink() }
|
||||
CorsOriginSink() { ModelOutput::sinkNode(this, "cors-origin") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -268,6 +268,6 @@ module ClientSideUrlRedirect {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "url-redirection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,6 +436,6 @@ module CodeInjection {
|
||||
class JsonStringifySanitizer extends Sanitizer, JsonStringifyCall { }
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("code-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "code-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,6 @@ module CommandInjection {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("command-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "command-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,6 +419,6 @@ module DomBasedXss {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("html-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "html-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,5 +86,5 @@ class JsonStringifySanitizer extends Sanitizer {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("log-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "log-injection") }
|
||||
}
|
||||
|
||||
@@ -145,6 +145,6 @@ module ReflectedXss {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("html-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "html-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ module RequestForgery {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("request-forgery").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "request-forgery") }
|
||||
|
||||
override DataFlow::Node getARequest() { result = this }
|
||||
|
||||
|
||||
@@ -64,6 +64,6 @@ module ServerSideUrlRedirect {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "url-redirection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1122,6 +1122,6 @@ module TaintedPath {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("path-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "path-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,6 @@ module UnsafeDeserialization {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "unsafe-deserialization") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ module TestConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source.(DataFlow::CallNode).getCalleeName() = "source"
|
||||
or
|
||||
source = ModelOutput::getASourceNode("test-source").asSource()
|
||||
ModelOutput::sourceNode(source, "test-source")
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument()
|
||||
or
|
||||
sink = ModelOutput::getASinkNode("test-sink").asSink()
|
||||
ModelOutput::sinkNode(sink, "test-sink")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,9 +48,7 @@ query predicate taintFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
TestFlow::flow(source, sink)
|
||||
}
|
||||
|
||||
query predicate isSink(DataFlow::Node node, string kind) {
|
||||
node = ModelOutput::getASinkNode(kind).asSink()
|
||||
}
|
||||
query predicate isSink(DataFlow::Node node, string kind) { ModelOutput::sinkNode(node, kind) }
|
||||
|
||||
query predicate syntaxErrors(ApiGraphModels::AccessPath path) { path.hasSyntaxError() }
|
||||
|
||||
|
||||
@@ -584,10 +584,6 @@ class GuardNode extends ControlFlowNode {
|
||||
|
||||
/**
|
||||
* Holds if the guard `g` validates `node` upon evaluating to `branch`.
|
||||
*
|
||||
* The expression `e` is expected to be a syntactic part of the guard `g`.
|
||||
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
|
||||
* the argument `x`.
|
||||
*/
|
||||
signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch);
|
||||
|
||||
@@ -600,15 +596,65 @@ signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean br
|
||||
module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
/** Gets a node that is safely guarded by the given guard check. */
|
||||
ExprNode getABarrierNode() {
|
||||
result = ParameterizedBarrierGuard<Unit, extendedGuardChecks/4>::getABarrierNode(_)
|
||||
}
|
||||
|
||||
private predicate extendedGuardChecks(GuardNode g, ControlFlowNode node, boolean branch, Unit u) {
|
||||
guardChecks(g, node, branch) and
|
||||
u = u
|
||||
}
|
||||
}
|
||||
|
||||
bindingset[this]
|
||||
private signature class ParamSig;
|
||||
|
||||
private module WithParam<ParamSig P> {
|
||||
signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch, P param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates a node.
|
||||
*
|
||||
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
|
||||
* in data flow and taint tracking.
|
||||
*/
|
||||
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
|
||||
/** Gets a node that is safely guarded by the given guard check with parameter `param`. */
|
||||
ExprNode getABarrierNode(P param) {
|
||||
exists(GuardNode g, EssaDefinition def, ControlFlowNode node, boolean branch |
|
||||
AdjacentUses::useOfDef(def, node) and
|
||||
guardChecks(g, node, branch) and
|
||||
guardChecks(g, node, branch, param) and
|
||||
AdjacentUses::useOfDef(def, result.asCfgNode()) and
|
||||
g.controlsBlock(result.asCfgNode().getBasicBlock(), branch)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates a node as described by an external predicate.
|
||||
*
|
||||
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
|
||||
* in data flow and taint tracking.
|
||||
*/
|
||||
module ExternalBarrierGuard {
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
private predicate guardCheck(GuardNode g, ControlFlowNode node, boolean branch, string kind) {
|
||||
exists(API::CallNode call, API::Node parameter |
|
||||
parameter = call.getAParameter() and
|
||||
parameter = ModelOutput::getABarrierGuardNode(kind, branch)
|
||||
|
|
||||
g = call.asCfgNode() and
|
||||
node = parameter.asSink().asCfgNode()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that is an external barrier of the given kind. */
|
||||
ExprNode getAnExternalBarrierNode(string kind) {
|
||||
result = ParameterizedBarrierGuard<string, guardCheck/4>::getABarrierNode(kind)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Algebraic datatype for tracking data content associated with values.
|
||||
* Content can be collection elements or object attributes.
|
||||
|
||||
6
python/ql/lib/semmle/python/frameworks/Django.model.yml
Normal file
6
python/ql/lib/semmle/python/frameworks/Django.model.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/python-all
|
||||
extensible: barrierGuardModel
|
||||
data:
|
||||
- ['django', 'Member[utils].Member[http].Member[url_has_allowed_host_and_scheme].Argument[0,url:]', "true", 'url-redirection']
|
||||
@@ -24,9 +24,9 @@ private import semmle.python.Concepts
|
||||
* A threat-model flow source originating from a data extension.
|
||||
*/
|
||||
private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Range {
|
||||
ThreatModelSourceFromDataExtension() { this = ModelOutput::getASourceNode(_).asSource() }
|
||||
ThreatModelSourceFromDataExtension() { ModelOutput::sourceNode(this, _) }
|
||||
|
||||
override string getThreatModel() { this = ModelOutput::getASourceNode(result).asSource() }
|
||||
override string getThreatModel() { ModelOutput::sourceNode(this, result) }
|
||||
|
||||
override string getSourceType() {
|
||||
result = "Source node (" + this.getThreatModel() + ") [from data-extension]"
|
||||
|
||||
@@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a barrier model exists for the given parameters. */
|
||||
private predicate barrierModel(string type, string path, string kind, string model) {
|
||||
// No deprecation adapter for barrier models, they were not around back then.
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::barrierModel(type, path, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a barrier guard model exists for the given parameters. */
|
||||
private predicate barrierGuardModel(
|
||||
string type, string path, string branch, string kind, string model
|
||||
) {
|
||||
// No deprecation adapter for barrier models, they were not around back then.
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::barrierGuardModel(type, path, branch, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a summary model `row` exists for the given parameters. */
|
||||
private predicate summaryModel(
|
||||
string type, string path, string input, string output, string kind, string model
|
||||
@@ -400,6 +420,8 @@ predicate isRelevantType(string type) {
|
||||
(
|
||||
sourceModel(type, _, _, _) or
|
||||
sinkModel(type, _, _, _) or
|
||||
barrierModel(type, _, _, _) or
|
||||
barrierGuardModel(type, _, _, _, _) or
|
||||
summaryModel(type, _, _, _, _, _) or
|
||||
typeModel(_, type, _)
|
||||
) and
|
||||
@@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) {
|
||||
(
|
||||
sourceModel(type, path, _, _) or
|
||||
sinkModel(type, path, _, _) or
|
||||
barrierModel(type, path, _, _) or
|
||||
barrierGuardModel(type, path, _, _, _) or
|
||||
summaryModel(type, path, _, _, _, _) or
|
||||
typeModel(_, type, path)
|
||||
)
|
||||
@@ -747,6 +771,32 @@ module ModelOutput {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a barrier model contributed `barrier` with the given `kind`.
|
||||
*/
|
||||
cached
|
||||
API::Node getABarrierNode(string kind, string model) {
|
||||
exists(string type, string path |
|
||||
barrierModel(type, path, kind, model) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a barrier model contributed `barrier` with the given `kind` for the given `branch`.
|
||||
*/
|
||||
cached
|
||||
API::Node getABarrierGuardNode(string kind, boolean branch, string model) {
|
||||
exists(string type, string path, string branch_str |
|
||||
branch = true and branch_str = "true"
|
||||
or
|
||||
branch = false and branch_str = "false"
|
||||
|
|
||||
barrierGuardModel(type, path, branch_str, kind, model) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a relevant summary exists for these parameters.
|
||||
*/
|
||||
@@ -789,15 +839,46 @@ module ModelOutput {
|
||||
private import codeql.mad.ModelValidation as SharedModelVal
|
||||
|
||||
/**
|
||||
* Holds if a CSV source model contributed `source` with the given `kind`.
|
||||
* Holds if an external model contributed `source` with the given `kind`.
|
||||
*/
|
||||
API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if a CSV sink model contributed `sink` with the given `kind`.
|
||||
* Holds if an external model contributed `sink` with the given `kind`.
|
||||
*/
|
||||
API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if an external model contributed `barrier` with the given `kind`.
|
||||
*/
|
||||
API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`.
|
||||
*/
|
||||
API::Node getABarrierGuardNode(string kind, boolean branch) {
|
||||
result = getABarrierGuardNode(kind, branch, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a source with the given kind in an external model.
|
||||
*/
|
||||
predicate sourceNode(DataFlow::Node node, string kind) { node = getASourceNode(kind).asSource() }
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a sink with the given kind in an external model.
|
||||
*/
|
||||
predicate sinkNode(DataFlow::Node node, string kind) { node = getASinkNode(kind).asSink() }
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a barrier with the given kind in an external model.
|
||||
*/
|
||||
predicate barrierNode(DataFlow::Node node, string kind) {
|
||||
node = getABarrierNode(kind).asSource()
|
||||
or
|
||||
node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind)
|
||||
}
|
||||
|
||||
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
|
||||
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }
|
||||
|
||||
|
||||
@@ -20,6 +20,26 @@ extensible predicate sourceModel(
|
||||
*/
|
||||
extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId);
|
||||
|
||||
/**
|
||||
* Holds if the value at `(type, path)` should be seen as a barrier
|
||||
* of the given `kind` and `madId` is the data extension row number.
|
||||
*/
|
||||
extensible predicate barrierModel(
|
||||
string type, string path, string kind, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if the value at `(type, path)` should be seen as a barrier guard
|
||||
* of the given `kind` and `madId` is the data extension row number.
|
||||
* `path` is assumed to lead to a parameter of a call (possibly `self`), and
|
||||
* the call is guarding the parameter.
|
||||
* `branch` is either `true` or `false`, indicating which branch of the guard
|
||||
* is protecting the parameter.
|
||||
*/
|
||||
extensible predicate barrierGuardModel(
|
||||
string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if in calls to `(type, path)`, the value referred to by `input`
|
||||
* can flow to the value referred to by `output` and `madId` is the data
|
||||
|
||||
@@ -11,6 +11,16 @@ extensions:
|
||||
extensible: sinkModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/python-all
|
||||
extensible: barrierModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/python-all
|
||||
extensible: barrierGuardModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/python-all
|
||||
extensible: summaryModel
|
||||
|
||||
@@ -50,7 +50,7 @@ module CodeInjection {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("code-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "code-injection") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,7 +85,7 @@ module CommandInjection {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("command-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "command-injection") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,7 +78,7 @@ module LogInjection {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("log-injection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "log-injection") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,7 +88,7 @@ module PathInjection {
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
private class DataAsFileSink extends Sink {
|
||||
DataAsFileSink() { this = ModelOutput::getASinkNode("path-injection").asSink() }
|
||||
DataAsFileSink() { ModelOutput::sinkNode(this, "path-injection") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,9 +46,7 @@ module ReflectedXss {
|
||||
* A data flow sink for "reflected cross-site scripting" vulnerabilities.
|
||||
*/
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() {
|
||||
this = ModelOutput::getASinkNode(["html-injection", "js-injection"]).asSink()
|
||||
}
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, ["html-injection", "js-injection"]) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,6 @@ module SqlInjection {
|
||||
|
||||
/** A sink for sql-injection from model data. */
|
||||
private class DataAsSqlSink extends Sink {
|
||||
DataAsSqlSink() { this = ModelOutput::getASinkNode("sql-injection").asSink() }
|
||||
DataAsSqlSink() { ModelOutput::sinkNode(this, "sql-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ module UnsafeDeserialization {
|
||||
}
|
||||
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "unsafe-deserialization") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
@@ -95,8 +96,11 @@ module UrlRedirect {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for URL redirection defined via models-as-data.
|
||||
*/
|
||||
private class SinkFromModel extends Sink {
|
||||
SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
|
||||
SinkFromModel() { ModelOutput::sinkNode(this, "url-redirection") }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,4 +160,18 @@ module UrlRedirect {
|
||||
|
||||
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
|
||||
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
|
||||
|
||||
/**
|
||||
* A sanitizer defined via models-as-data with kind "url-redirection".
|
||||
*/
|
||||
class SanitizerFromModel extends Sanitizer {
|
||||
SanitizerFromModel() {
|
||||
this = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode("url-redirection")
|
||||
}
|
||||
|
||||
override predicate sanitizes(FlowState state) {
|
||||
// sanitize all flow states
|
||||
any()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class CredentialSink extends DataFlow::Node {
|
||||
CredentialSink() {
|
||||
exists(string s | s.matches("credentials-%") |
|
||||
// Actual sink-type will be things like `credentials-password` or `credentials-username`
|
||||
this = ModelOutput::getASinkNode(s).asSink()
|
||||
ModelOutput::sinkNode(this, s)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
|
||||
@@ -17,7 +17,7 @@ module MadSinkTest implements TestSig {
|
||||
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(location.getFile().getRelativePath()) and
|
||||
exists(DataFlow::Node sink, string kind |
|
||||
sink = ModelOutput::getASinkNode(kind).asSink() and
|
||||
ModelOutput::sinkNode(sink, kind) and
|
||||
location = sink.getLocation() and
|
||||
element = sink.toString() and
|
||||
value = prettyNodeForInlineTest(sink) and
|
||||
@@ -34,7 +34,7 @@ module MadSourceTest implements TestSig {
|
||||
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(location.getFile().getRelativePath()) and
|
||||
exists(DataFlow::Node source, string kind |
|
||||
source = ModelOutput::getASourceNode(kind).asSource() and
|
||||
ModelOutput::sourceNode(source, kind) and
|
||||
location = source.getLocation() and
|
||||
element = source.toString() and
|
||||
value = prettyNodeForInlineTest(source) and
|
||||
|
||||
@@ -75,7 +75,7 @@ edges
|
||||
| UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | provenance | |
|
||||
| UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | provenance | |
|
||||
| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | MaD:69 |
|
||||
| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | MaD:70 |
|
||||
| UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | UnsafeUnpack.py:166:37:166:42 | ControlFlowNode for member | provenance | |
|
||||
| UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | provenance | |
|
||||
| UnsafeUnpack.py:166:23:166:28 | [post] ControlFlowNode for result | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | provenance | |
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
edges
|
||||
| test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:11:21:11:29 | ControlFlowNode for file_path | provenance | |
|
||||
| test.py:11:5:11:35 | ControlFlowNode for Attribute() | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:35 | ControlFlowNode for Attribute() | provenance | MaD:86 |
|
||||
| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:35 | ControlFlowNode for Attribute() | provenance | MaD:87 |
|
||||
| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:12:21:12:29 | ControlFlowNode for file_path | provenance | |
|
||||
| test.py:12:5:12:35 | ControlFlowNode for Attribute() | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:35 | ControlFlowNode for Attribute() | provenance | MaD:86 |
|
||||
| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:35 | ControlFlowNode for Attribute() | provenance | MaD:87 |
|
||||
| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:14:26:14:34 | ControlFlowNode for file_path | provenance | |
|
||||
| test.py:14:10:14:35 | ControlFlowNode for Attribute() | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:14:10:14:35 | ControlFlowNode for Attribute() | provenance | MaD:86 |
|
||||
| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:14:10:14:35 | ControlFlowNode for Attribute() | provenance | MaD:87 |
|
||||
| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:18:26:18:34 | ControlFlowNode for file_path | provenance | |
|
||||
| test.py:18:10:18:35 | ControlFlowNode for Attribute() | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:18:10:18:35 | ControlFlowNode for Attribute() | provenance | MaD:86 |
|
||||
| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:18:10:18:35 | ControlFlowNode for Attribute() | provenance | MaD:87 |
|
||||
| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:22:21:22:29 | ControlFlowNode for file_path | provenance | |
|
||||
| test.py:22:5:22:30 | ControlFlowNode for Attribute() | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:30 | ControlFlowNode for Attribute() | provenance | MaD:86 |
|
||||
| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:30 | ControlFlowNode for Attribute() | provenance | MaD:87 |
|
||||
| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:24:18:24:26 | ControlFlowNode for file_path | provenance | |
|
||||
| test.py:24:18:24:26 | ControlFlowNode for file_path | test.py:24:5:24:52 | ControlFlowNode for Attribute() | provenance | Config |
|
||||
|
||||
@@ -6,11 +6,9 @@ import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
module BasicTaintTrackingConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source = ModelOutput::getASourceNode("test-source").asSource()
|
||||
}
|
||||
predicate isSource(DataFlow::Node source) { ModelOutput::sourceNode(source, "test-source") }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink = ModelOutput::getASinkNode("test-sink").asSink() }
|
||||
predicate isSink(DataFlow::Node sink) { ModelOutput::sinkNode(sink, "test-sink") }
|
||||
}
|
||||
|
||||
module TestTaintTrackingFlow = TaintTracking::Global<BasicTaintTrackingConfig>;
|
||||
@@ -19,13 +17,9 @@ query predicate taintFlow(DataFlow::Node source, DataFlow::Node sink) {
|
||||
TestTaintTrackingFlow::flow(source, sink)
|
||||
}
|
||||
|
||||
query predicate isSink(DataFlow::Node node, string kind) {
|
||||
node = ModelOutput::getASinkNode(kind).asSink()
|
||||
}
|
||||
query predicate isSink(DataFlow::Node node, string kind) { ModelOutput::sinkNode(node, kind) }
|
||||
|
||||
query predicate isSource(DataFlow::Node node, string kind) {
|
||||
node = ModelOutput::getASourceNode(kind).asSource()
|
||||
}
|
||||
query predicate isSource(DataFlow::Node node, string kind) { ModelOutput::sourceNode(node, kind) }
|
||||
|
||||
query predicate syntaxErrors(ApiGraphModels::AccessPath path) { path.hasSyntaxError() }
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
edges
|
||||
| test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | provenance | Src:MaD:17 |
|
||||
| test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | provenance | Src:MaD:18 |
|
||||
nodes
|
||||
| test.py:6:14:6:21 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| test.py:6:14:6:24 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
|
||||
@@ -14,10 +14,10 @@ edges
|
||||
| http_test.py:5:16:5:19 | ControlFlowNode for self | http_test.py:6:45:6:53 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
|
||||
| http_test.py:6:9:6:19 | ControlFlowNode for parsed_path | http_test.py:7:40:7:56 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
|
||||
| http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | http_test.py:6:9:6:19 | ControlFlowNode for parsed_path | provenance | |
|
||||
| http_test.py:6:45:6:53 | ControlFlowNode for Attribute | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | provenance | MaD:77 |
|
||||
| http_test.py:6:45:6:53 | ControlFlowNode for Attribute | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | provenance | MaD:78 |
|
||||
| http_test.py:7:9:7:14 | ControlFlowNode for params | http_test.py:8:23:8:28 | ControlFlowNode for params | provenance | |
|
||||
| http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | http_test.py:7:9:7:14 | ControlFlowNode for params | provenance | |
|
||||
| http_test.py:7:40:7:56 | ControlFlowNode for Attribute | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | provenance | MaD:76 |
|
||||
| http_test.py:7:40:7:56 | ControlFlowNode for Attribute | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | provenance | MaD:77 |
|
||||
| http_test.py:8:9:8:19 | ControlFlowNode for input_value | http_test.py:12:40:12:50 | ControlFlowNode for input_value | provenance | |
|
||||
| http_test.py:8:23:8:28 | ControlFlowNode for params | http_test.py:8:23:8:47 | ControlFlowNode for Attribute() | provenance | dict.get |
|
||||
| http_test.py:8:23:8:47 | ControlFlowNode for Attribute() | http_test.py:8:9:8:19 | ControlFlowNode for input_value | provenance | |
|
||||
|
||||
@@ -52,7 +52,6 @@ edges
|
||||
| test.py:81:17:81:46 | ControlFlowNode for Attribute() | test.py:81:5:81:13 | ControlFlowNode for untrusted | provenance | |
|
||||
| test.py:82:5:82:10 | ControlFlowNode for unsafe | test.py:83:21:83:26 | ControlFlowNode for unsafe | provenance | |
|
||||
| test.py:90:5:90:13 | ControlFlowNode for untrusted | test.py:93:18:93:26 | ControlFlowNode for untrusted | provenance | |
|
||||
| test.py:90:5:90:13 | ControlFlowNode for untrusted | test.py:95:25:95:33 | ControlFlowNode for untrusted | provenance | |
|
||||
| test.py:90:17:90:23 | ControlFlowNode for request | test.py:90:17:90:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
|
||||
| test.py:90:17:90:28 | ControlFlowNode for Attribute | test.py:90:17:90:46 | ControlFlowNode for Attribute() | provenance | dict.get |
|
||||
| test.py:90:17:90:46 | ControlFlowNode for Attribute() | test.py:90:5:90:13 | ControlFlowNode for untrusted | provenance | |
|
||||
@@ -123,7 +122,6 @@ nodes
|
||||
| test.py:90:17:90:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| test.py:90:17:90:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| test.py:93:18:93:26 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted |
|
||||
| test.py:95:25:95:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted |
|
||||
| test.py:111:5:111:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted |
|
||||
| test.py:111:17:111:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| test.py:111:17:111:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
@@ -150,7 +148,6 @@ subpaths
|
||||
| test.py:76:21:76:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:76:21:76:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:83:21:83:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:83:21:83:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:93:18:93:26 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:93:18:93:26 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:95:25:95:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:95:25:95:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:114:25:114:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:114:25:114:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:140:25:140:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:140:25:140:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:148:25:148:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:148:25:148:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
|
||||
@@ -927,6 +927,85 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
}
|
||||
}
|
||||
|
||||
bindingset[this]
|
||||
private signature class ParamSig;
|
||||
|
||||
private module WithParam<ParamSig P> {
|
||||
/**
|
||||
* Holds if the guard `g` validates the expression `e` upon evaluating to `branch`.
|
||||
*
|
||||
* The expression `e` is expected to be a syntactic part of the guard `g`.
|
||||
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
|
||||
* the argument `x`.
|
||||
*/
|
||||
signature predicate guardChecksSig(CfgNodes::AstCfgNode g, CfgNode e, boolean branch, P param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates a node.
|
||||
*
|
||||
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
|
||||
* in data flow and taint tracking.
|
||||
*/
|
||||
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
|
||||
private import codeql.ruby.controlflow.internal.Guards
|
||||
|
||||
/**
|
||||
* Gets an implicit entry definition for a captured variable that
|
||||
* may be guarded, because a call to the capturing callable is guarded.
|
||||
*
|
||||
* This is restricted to calls where the variable is captured inside a
|
||||
* block.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private Ssa::CapturedEntryDefinition getAMaybeGuardedCapturedDef(P param) {
|
||||
exists(
|
||||
CfgNodes::ExprCfgNode g, boolean branch, CfgNodes::ExprCfgNode testedNode,
|
||||
Ssa::Definition def, CfgNodes::ExprNodes::CallCfgNode call
|
||||
|
|
||||
def.getARead() = testedNode and
|
||||
guardChecks(g, testedNode, branch, param) and
|
||||
guardControlsBlock(g, call.getBasicBlock(), branch) and
|
||||
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock() and
|
||||
sameSourceVariable(def, result)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that is safely guarded by the given guard check. */
|
||||
Node getABarrierNode(P param) {
|
||||
SsaFlow::asNode(result) =
|
||||
SsaImpl::DataFlowIntegration::ParameterizedBarrierGuard<P, guardChecks/4>::getABarrierNode(param)
|
||||
or
|
||||
result.asExpr() = getAMaybeGuardedCapturedDef(param).getARead()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates a node as described by an external predicate.
|
||||
*
|
||||
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
|
||||
* in data flow and taint tracking.
|
||||
*/
|
||||
module ExternalBarrierGuard {
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
|
||||
private predicate guardCheck(CfgNodes::AstCfgNode g, CfgNode e, boolean branch, string kind) {
|
||||
// (GuardNode g, ControlFlowNode node, boolean branch, string kind) {
|
||||
exists(API::Node call, API::Node parameter |
|
||||
parameter.asSink() = call.asCall().getArgument(_) and
|
||||
parameter = ModelOutput::getABarrierGuardNode(kind, branch)
|
||||
|
|
||||
g = call.asCall().asExpr() and
|
||||
e = parameter.asSink().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that is an external barrier of the given kind. */
|
||||
ExprNode getAnExternalBarrierNode(string kind) {
|
||||
result = ParameterizedBarrierGuard<string, guardCheck/4>::getABarrierNode(kind)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a run-time module or class.
|
||||
*
|
||||
|
||||
@@ -403,6 +403,33 @@ private module Cached {
|
||||
|
||||
predicate getABarrierNode = getABarrierNodeImpl/0;
|
||||
}
|
||||
|
||||
bindingset[this]
|
||||
private signature class ParamSig;
|
||||
|
||||
private module WithParam<ParamSig P> {
|
||||
signature predicate guardChecksSig(
|
||||
Cfg::CfgNodes::AstCfgNode g, Cfg::CfgNode e, boolean branch, P param
|
||||
);
|
||||
}
|
||||
|
||||
overlay[global]
|
||||
cached // nothing is actually cached
|
||||
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
|
||||
private predicate guardChecksAdjTypes(
|
||||
DataFlowIntegrationInput::Guard g, DataFlowIntegrationInput::Expr e,
|
||||
DataFlowIntegrationInput::GuardValue branch, P param
|
||||
) {
|
||||
guardChecks(g, e, branch, param)
|
||||
}
|
||||
|
||||
private Node getABarrierNodeImpl(P param) {
|
||||
result =
|
||||
DataFlowIntegrationImpl::BarrierGuardWithState<P, guardChecksAdjTypes/4>::getABarrierNode(param)
|
||||
}
|
||||
|
||||
predicate getABarrierNode = getABarrierNodeImpl/1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ private import codeql.ruby.dataflow.FlowSummary
|
||||
* A remote flow source originating from a CSV source row.
|
||||
*/
|
||||
private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range {
|
||||
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").asSource() }
|
||||
RemoteFlowSourceFromCsv() { ModelOutput::sourceNode(this, "remote") }
|
||||
|
||||
override string getSourceType() { result = "Remote flow (from model)" }
|
||||
}
|
||||
|
||||
@@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a barrier model exists for the given parameters. */
|
||||
private predicate barrierModel(string type, string path, string kind, string model) {
|
||||
// No deprecation adapter for barrier models, they were not around back then.
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::barrierModel(type, path, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a barrier guard model exists for the given parameters. */
|
||||
private predicate barrierGuardModel(
|
||||
string type, string path, string branch, string kind, string model
|
||||
) {
|
||||
// No deprecation adapter for barrier models, they were not around back then.
|
||||
exists(QlBuiltins::ExtensionId madId |
|
||||
Extensions::barrierGuardModel(type, path, branch, kind, madId) and
|
||||
model = "MaD:" + madId.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a summary model `row` exists for the given parameters. */
|
||||
private predicate summaryModel(
|
||||
string type, string path, string input, string output, string kind, string model
|
||||
@@ -400,6 +420,8 @@ predicate isRelevantType(string type) {
|
||||
(
|
||||
sourceModel(type, _, _, _) or
|
||||
sinkModel(type, _, _, _) or
|
||||
barrierModel(type, _, _, _) or
|
||||
barrierGuardModel(type, _, _, _, _) or
|
||||
summaryModel(type, _, _, _, _, _) or
|
||||
typeModel(_, type, _)
|
||||
) and
|
||||
@@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) {
|
||||
(
|
||||
sourceModel(type, path, _, _) or
|
||||
sinkModel(type, path, _, _) or
|
||||
barrierModel(type, path, _, _) or
|
||||
barrierGuardModel(type, path, _, _, _) or
|
||||
summaryModel(type, path, _, _, _, _) or
|
||||
typeModel(_, type, path)
|
||||
)
|
||||
@@ -747,6 +771,32 @@ module ModelOutput {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a barrier model contributed `barrier` with the given `kind`.
|
||||
*/
|
||||
cached
|
||||
API::Node getABarrierNode(string kind, string model) {
|
||||
exists(string type, string path |
|
||||
barrierModel(type, path, kind, model) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a barrier model contributed `barrier` with the given `kind` for the given `branch`.
|
||||
*/
|
||||
cached
|
||||
API::Node getABarrierGuardNode(string kind, boolean branch, string model) {
|
||||
exists(string type, string path, string branch_str |
|
||||
branch = true and branch_str = "true"
|
||||
or
|
||||
branch = false and branch_str = "false"
|
||||
|
|
||||
barrierGuardModel(type, path, branch_str, kind, model) and
|
||||
result = getNodeFromPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a relevant summary exists for these parameters.
|
||||
*/
|
||||
@@ -789,15 +839,46 @@ module ModelOutput {
|
||||
private import codeql.mad.ModelValidation as SharedModelVal
|
||||
|
||||
/**
|
||||
* Holds if a CSV source model contributed `source` with the given `kind`.
|
||||
* Holds if an external model contributed `source` with the given `kind`.
|
||||
*/
|
||||
API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if a CSV sink model contributed `sink` with the given `kind`.
|
||||
* Holds if an external model contributed `sink` with the given `kind`.
|
||||
*/
|
||||
API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if an external model contributed `barrier` with the given `kind`.
|
||||
*/
|
||||
API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) }
|
||||
|
||||
/**
|
||||
* Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`.
|
||||
*/
|
||||
API::Node getABarrierGuardNode(string kind, boolean branch) {
|
||||
result = getABarrierGuardNode(kind, branch, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a source with the given kind in an external model.
|
||||
*/
|
||||
predicate sourceNode(DataFlow::Node node, string kind) { node = getASourceNode(kind).asSource() }
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a sink with the given kind in an external model.
|
||||
*/
|
||||
predicate sinkNode(DataFlow::Node node, string kind) { node = getASinkNode(kind).asSink() }
|
||||
|
||||
/**
|
||||
* Holds if `node` is specified as a barrier with the given kind in an external model.
|
||||
*/
|
||||
predicate barrierNode(DataFlow::Node node, string kind) {
|
||||
node = getABarrierNode(kind).asSource()
|
||||
or
|
||||
node = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode(kind)
|
||||
}
|
||||
|
||||
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
|
||||
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }
|
||||
|
||||
|
||||
@@ -20,6 +20,26 @@ extensible predicate sourceModel(
|
||||
*/
|
||||
extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId);
|
||||
|
||||
/**
|
||||
* Holds if the value at `(type, path)` should be seen as a barrier
|
||||
* of the given `kind` and `madId` is the data extension row number.
|
||||
*/
|
||||
extensible predicate barrierModel(
|
||||
string type, string path, string kind, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if the value at `(type, path)` should be seen as a barrier guard
|
||||
* of the given `kind` and `madId` is the data extension row number.
|
||||
* `path` is assumed to lead to a parameter of a call (possibly `self`), and
|
||||
* the call is guarding the parameter.
|
||||
* `branch` is either `true` or `false`, indicating which branch of the guard
|
||||
* is protecting the parameter.
|
||||
*/
|
||||
extensible predicate barrierGuardModel(
|
||||
string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds if in calls to `(type, path)`, the value referred to by `input`
|
||||
* can flow to the value referred to by `output` and `madId` is the data
|
||||
|
||||
@@ -16,6 +16,16 @@ extensions:
|
||||
extensible: summaryModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/ruby-all
|
||||
extensible: barrierModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/ruby-all
|
||||
extensible: barrierGuardModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/ruby-all
|
||||
extensible: neutralModel
|
||||
|
||||
@@ -116,6 +116,6 @@ module CodeInjection {
|
||||
}
|
||||
|
||||
private class ExternalCodeInjectionSink extends Sink {
|
||||
ExternalCodeInjectionSink() { this = ModelOutput::getASinkNode("code-injection").asSink() }
|
||||
ExternalCodeInjectionSink() { ModelOutput::sinkNode(this, "code-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,6 @@ module CommandInjection {
|
||||
}
|
||||
|
||||
private class ExternalCommandInjectionSink extends Sink {
|
||||
ExternalCommandInjectionSink() {
|
||||
this = ModelOutput::getASinkNode("command-injection").asSink()
|
||||
}
|
||||
ExternalCommandInjectionSink() { ModelOutput::sinkNode(this, "command-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class LoggingSink extends Sink {
|
||||
}
|
||||
|
||||
private class ExternalLogInjectionSink extends Sink {
|
||||
ExternalLogInjectionSink() { this = ModelOutput::getASinkNode("log-injection").asSink() }
|
||||
ExternalLogInjectionSink() { ModelOutput::sinkNode(this, "log-injection") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,6 @@ module PathInjection {
|
||||
{ }
|
||||
|
||||
private class ExternalPathInjectionSink extends Sink {
|
||||
ExternalPathInjectionSink() { this = ModelOutput::getASinkNode("path-injection").asSink() }
|
||||
ExternalPathInjectionSink() { ModelOutput::sinkNode(this, "path-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,6 @@ module ServerSideRequestForgery {
|
||||
class StringInterpolationAsSanitizer extends PrefixedStringInterpolation, Sanitizer { }
|
||||
|
||||
private class ExternalRequestForgerySink extends Sink {
|
||||
ExternalRequestForgerySink() { this = ModelOutput::getASinkNode("request-forgery").asSink() }
|
||||
ExternalRequestForgerySink() { ModelOutput::sinkNode(this, "request-forgery") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,6 @@ module SqlInjection {
|
||||
private class SqlSanitizationAsSanitizer extends Sanitizer, SqlSanitization { }
|
||||
|
||||
private class ExternalSqlInjectionSink extends Sink {
|
||||
ExternalSqlInjectionSink() { this = ModelOutput::getASinkNode("sql-injection").asSink() }
|
||||
ExternalSqlInjectionSink() { ModelOutput::sinkNode(this, "sql-injection") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ module UrlRedirect {
|
||||
}
|
||||
|
||||
private class ExternalUrlRedirectSink extends Sink {
|
||||
ExternalUrlRedirectSink() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
|
||||
ExternalUrlRedirectSink() { ModelOutput::sinkNode(this, "url-redirection") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,7 +80,7 @@ module CustomConfig implements DataFlow::ConfigSig {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
DefaultFlowConfig::isSink(sink)
|
||||
or
|
||||
sink = ModelOutput::getASinkNode("test-sink").asSink()
|
||||
ModelOutput::sinkNode(sink, "test-sink")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user