Merge branch 'main' of github.com:github/codeql into python-port-stacktrace-exosure

This commit is contained in:
Rasmus Lerchedahl Petersen
2021-02-25 07:24:41 +01:00
667 changed files with 32683 additions and 11632 deletions

View File

@@ -41,6 +41,7 @@ private import semmle.python.objects.ObjectInternal
* A callable that is considered a "safe" external API from a security perspective.
*/
class SafeExternalAPI extends Unit {
/** Gets a callable that is considered a "safe" external API from a security perspective. */
abstract DataFlowPrivate::DataFlowCallable getSafeCallable();
}

View File

@@ -10,25 +10,25 @@
*/
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
private ModuleValue theParamikoClientModule() { result = Value::named("paramiko.client") }
private ClassValue theParamikoSSHClientClass() {
result = theParamikoClientModule().attr("SSHClient")
private API::Node unsafe_paramiko_policy(string name) {
name in ["AutoAddPolicy", "WarningPolicy"] and
result = API::moduleImport("paramiko").getMember("client").getMember(name)
}
private ClassValue unsafe_paramiko_policy(string name) {
(name = "AutoAddPolicy" or name = "WarningPolicy") and
result = theParamikoClientModule().attr(name)
private API::Node paramikoSSHClientInstance() {
result = API::moduleImport("paramiko").getMember("client").getMember("SSHClient").getReturn()
}
from CallNode call, ControlFlowNode arg, string name
from DataFlow::CallCfgNode call, DataFlow::Node arg, string name
where
call =
theParamikoSSHClientClass().lookup("set_missing_host_key_policy").(FunctionValue).getACall() and
arg = call.getAnArg() and
// see http://docs.paramiko.org/en/stable/api/client.html#paramiko.client.SSHClient.set_missing_host_key_policy
call = paramikoSSHClientInstance().getMember("set_missing_host_key_policy").getACall() and
arg in [call.getArg(0), call.getArgByName("policy")] and
(
arg.pointsTo(unsafe_paramiko_policy(name)) or
arg.pointsTo().getClass() = unsafe_paramiko_policy(name)
arg = unsafe_paramiko_policy(name).getAUse() or
arg = unsafe_paramiko_policy(name).getReturn().getAUse()
)
select call, "Setting missing host key policy to " + name + " may be unsafe."

View File

@@ -11,8 +11,9 @@
*/
import python
import semmle.python.ApiGraphs
FunctionValue temporary_name_function(string mod, string function) {
API::Node temporary_name_function(string mod, string function) {
(
mod = "tempfile" and function = "mktemp"
or
@@ -23,9 +24,9 @@ FunctionValue temporary_name_function(string mod, string function) {
function = "tempnam"
)
) and
result = Module::named(mod).attr(function)
result = API::moduleImport(mod).getMember(function)
}
from Call c, string mod, string function
where temporary_name_function(mod, function).getACall().getNode() = c
where temporary_name_function(mod, function).getACall().asExpr() = c
select c, "Call to deprecated function " + mod + "." + function + " may be insecure."

View File

@@ -24,6 +24,9 @@ Avoid deserialization of untrusted data if at all possible. If the
architecture permits it then use other formats instead of serialized objects,
for example JSON.
</p>
<p>
If you need to use YAML, use the <code>yaml.safe_load</code> function.
</p>
</recommendation>
<example>

View File

@@ -92,6 +92,11 @@ module API {
*/
Node getReturn() { result = getASuccessor(Label::return()) }
/**
* Gets a node representing a subclass of the class represented by this node.
*/
Node getASubclass() { result = getASuccessor(Label::subclass()) }
/**
* Gets a string representation of the lexicographically least among all shortest access paths
* from the root to this node.
@@ -312,12 +317,11 @@ module API {
* For instance, `prefix_member("foo.bar", "baz", "foo.bar.baz")` would hold.
*/
private predicate prefix_member(TApiNode base, string member, TApiNode sub) {
exists(string base_str, string sub_str |
base = MkModuleImport(base_str) and
exists(string sub_str, string regexp |
regexp = "(.+)[.]([^.]+)" and
base = MkModuleImport(sub_str.regexpCapture(regexp, 1)) and
member = sub_str.regexpCapture(regexp, 2) and
sub = MkModuleImport(sub_str)
|
base_str + "." + member = sub_str and
not member.matches("%.%")
)
}
@@ -351,13 +355,19 @@ module API {
// the relationship between `pred` and `ref`.
use(base, src) and pred = trackUseNode(src)
|
// Reading an attribute on a node that is a use of `base`:
// Referring to an attribute on a node that is a use of `base`:
lbl = Label::memberFromRef(ref) and
ref = pred.getAnAttributeRead()
ref = pred.getAnAttributeReference()
or
// Calling a node that is a use of `base`
lbl = Label::return() and
ref = pred.getACall()
or
// Subclassing a node
lbl = Label::subclass() and
exists(DataFlow::Node superclass | pred.flowsTo(superclass) |
ref.asExpr().(ClassExpr).getABase() = superclass.asExpr()
)
)
}
@@ -468,4 +478,6 @@ private module Label {
/** Gets the `return` edge label. */
string return() { result = "getReturn()" }
string subclass() { result = "getASubclass()" }
}

View File

@@ -373,6 +373,9 @@ module HTTP {
* requests for this route, if any. These automatically become a `RemoteFlowSource`.
*/
Parameter getARoutedParameter() { result = range.getARoutedParameter() }
/** Gets a string that identifies the framework used for this route setup. */
string getFramework() { result = range.getFramework() }
}
/** Provides a class for modeling new HTTP routing APIs. */
@@ -407,6 +410,9 @@ module HTTP {
* requests for this route, if any. These automatically become a `RemoteFlowSource`.
*/
abstract Parameter getARoutedParameter();
/** Gets a string that identifies the framework used for this route setup. */
abstract string getFramework();
}
}
@@ -426,6 +432,9 @@ module HTTP {
* requests, if any. These automatically become a `RemoteFlowSource`.
*/
Parameter getARoutedParameter() { result = range.getARoutedParameter() }
/** Gets a string that identifies the framework used for this route setup. */
string getFramework() { result = range.getFramework() }
}
/** Provides a class for modeling new HTTP request handlers. */
@@ -444,6 +453,9 @@ module HTTP {
* requests, if any. These automatically become a `RemoteFlowSource`.
*/
abstract Parameter getARoutedParameter();
/** Gets a string that identifies the framework used for this request handler. */
abstract string getFramework();
}
}
@@ -456,13 +468,17 @@ module HTTP {
result = rs.getARoutedParameter() and
result in [this.getArg(_), this.getArgByName(_)]
}
override string getFramework() { result = rs.getFramework() }
}
/** A parameter that will receive parts of the url when handling an incoming request. */
private class RoutedParameter extends RemoteFlowSource::Range, DataFlow::ParameterNode {
RoutedParameter() { this.getParameter() = any(RequestHandler handler).getARoutedParameter() }
RequestHandler handler;
override string getSourceType() { result = "RoutedParameter" }
RoutedParameter() { this.getParameter() = handler.getARoutedParameter() }
override string getSourceType() { result = handler.getFramework() + " RoutedParameter" }
}
/**

View File

@@ -180,7 +180,7 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalAttributeN
* It is recommended that all uses of this type are written in the following form,
* for tracking some type `myType`:
* ```
* DataFlow::Node myType(DataFlow::TypeTracker t) {
* DataFlow::LocalSourceNode myType(DataFlow::TypeTracker t) {
* t.start() and
* result = < source of myType >
* or
@@ -189,7 +189,7 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalAttributeN
* )
* }
*
* DataFlow::Node myType() { result = myType(DataFlow::TypeTracker::end()) }
* DataFlow::Node myType() { myType(DataFlow::TypeTracker::end()).flowsTo(result) }
* ```
*
* Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent
@@ -324,3 +324,144 @@ module TypeTracker {
*/
TypeTracker end() { result.end() }
}
private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalAttributeName attr)
/**
* Summary of the steps needed to back-track a use of a value to a given dataflow node.
*
* This can for example be used to track callbacks that are passed to a certain API,
* so we can model specific parameters of that callback as having a certain type.
*
* Note that type back-tracking does not provide a source/sink relation, that is,
* it may determine that a node will be used in an API call somewhere, but it won't
* determine exactly where that use was, or the path that led to the use.
*
* It is recommended that all uses of this type are written in the following form,
* for back-tracking some callback type `myCallback`:
*
* ```
* DataFlow::LocalSourceNode myCallback(DataFlow::TypeBackTracker t) {
* t.start() and
* result = (< some API call >).getArgument(< n >).getALocalSource()
* or
* exists (DataFlow::TypeBackTracker t2 |
* result = myCallback(t2).backtrack(t2, t)
* )
* }
*
* DataFlow::LocalSourceNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) }
* ```
*
* Instead of `result = myCallback(t2).backtrack(t2, t)`, you can also use the equivalent
* `t2 = t.step(result, myCallback(t2))`. If you additionally want to track individual
* intra-procedural steps, use `t2 = t.smallstep(result, myCallback(t2))`.
*/
class TypeBackTracker extends TTypeBackTracker {
Boolean hasReturn;
string attr;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, attr) }
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
TypeBackTracker prepend(StepSummary step) {
step = LevelStep() and result = this
or
step = CallStep() and hasReturn = false and result = this
or
step = ReturnStep() and result = MkTypeBackTracker(true, attr)
or
exists(string p | step = LoadStep(p) and attr = "" and result = MkTypeBackTracker(hasReturn, p))
or
step = StoreStep(attr) and result = MkTypeBackTracker(hasReturn, "")
}
/** Gets a textual representation of this summary. */
string toString() {
exists(string withReturn, string withAttr |
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
(if attr != "" then withAttr = " with attribute " + attr else withAttr = "") and
result = "type back-tracker " + withReturn + " return steps" + withAttr
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasReturn = false and attr = "" }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { attr = "" }
/**
* INTERNAL. DO NOT USE.
*
* Holds if this type has been back-tracked into a call through return edge.
*/
boolean hasReturn() { result = hasReturn }
/**
* Gets a type tracker that starts where this one has left off to allow continued
* tracking.
*
* This predicate is only defined if the type has not been tracked into an attribute.
*/
TypeBackTracker continue() { attr = "" and result = this }
/**
* Gets the summary that corresponds to having taken a backwards
* heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
*/
pragma[inline]
TypeBackTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
exists(StepSummary summary |
StepSummary::step(nodeFrom, nodeTo, summary) and
this = result.prepend(summary)
)
}
/**
* Gets the summary that corresponds to having taken a backwards
* local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
*
* Unlike `TypeBackTracker::step`, this predicate exposes all edges
* in the flowgraph, and not just the edges between
* `LocalSourceNode`s. It may therefore be less performant.
*
* Type tracking predicates using small steps typically take the following form:
* ```ql
* DataFlow::Node myType(DataFlow::TypeBackTracker t) {
* t.start() and
* result = < some API call >.getArgument(< n >)
* or
* exists (DataFlow::TypeBackTracker t2 |
* t = t2.smallstep(result, myType(t2))
* )
* }
*
* DataFlow::Node myType() {
* result = myType(DataFlow::TypeBackTracker::end())
* }
* ```
*/
pragma[inline]
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
exists(StepSummary summary |
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
this = result.prepend(summary)
)
or
typePreservingStep(nodeFrom, nodeTo) and
this = result
}
}
/** Provides predicates for implementing custom `TypeBackTracker`s. */
module TypeBackTracker {
/**
* Gets a valid end point of type back-tracking.
*/
TypeBackTracker end() { result.end() }
}

View File

@@ -159,7 +159,9 @@ private class SetAttrCallAsAttrWrite extends AttrWrite, CfgNode {
* Instances of this class correspond to the `NameNode` for `attr`, and also gives access to `value` by
* virtue of being a `DefinitionNode`.
*/
private class ClassAttributeAssignmentNode extends DefinitionNode, NameNode { }
private class ClassAttributeAssignmentNode extends DefinitionNode, NameNode {
ClassAttributeAssignmentNode() { this.getScope() = any(ClassExpr c).getInnerScope() }
}
/**
* An attribute assignment via a class field, e.g.

View File

@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()

View File

@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()

View File

@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()

View File

@@ -3598,6 +3598,7 @@ private module FlowExploration {
or
exists(PartialPathNodeRev mid |
revPartialPathStep(mid, node, sc1, sc2, ap, config) and
not clearsContent(node, ap.getHead()) and
not fullBarrier(node, config) and
distSink(node.getEnclosingCallable(), config) <= config.explorationLimit()
)
@@ -3611,6 +3612,7 @@ private module FlowExploration {
exists(PartialPathNodeFwd mid |
partialPathStep(mid, node, cc, sc1, sc2, ap, config) and
not fullBarrier(node, config) and
not clearsContent(node, ap.getHead().getContent()) and
if node instanceof CastingNode
then compatibleTypes(getNodeType(node), ap.getType())
else any()

View File

@@ -1508,6 +1508,8 @@ predicate forReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
c instanceof ListElementContent
or
c instanceof SetElementContent
or
c instanceof TupleElementContent
)
}

View File

@@ -125,6 +125,19 @@ class Node extends TNode {
*/
pragma[inline]
Node track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
/**
* Gets a node that may flow into this one using one heap and/or interprocedural step.
*
* See `TypeBackTracker` for more details about how to use this.
*/
pragma[inline]
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
/**
* Gets a local source node from which data may flow to this node in zero or more local steps.
*/
LocalSourceNode getALocalSource() { result.flowsTo(this) }
}
/** A data-flow node corresponding to an SSA variable. */
@@ -165,6 +178,23 @@ class CfgNode extends Node, TCfgNode {
override Location getLocation() { result = node.getLocation() }
}
/** A data-flow node corresponding to a `CallNode` in the control-flow graph. */
class CallCfgNode extends CfgNode {
override CallNode node;
/**
* Gets the data-flow node for the function component of the call corresponding to this data-flow
* node.
*/
Node getFunction() { result.asCfgNode() = node.getFunction() }
/** Gets the data-flow node corresponding to the i'th argument of the call corresponding to this data-flow node */
Node getArg(int i) { result.asCfgNode() = node.getArg(i) }
/** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
Node getArgByName(string name) { result.asCfgNode() = node.getArgByName(name) }
}
/**
* An expression, viewed as a node in a data flow graph.
*
@@ -481,7 +511,7 @@ class LocalSourceNode extends Node {
/**
* Gets a call to this node.
*/
Node getACall() { Cached::call(this, result) }
CallCfgNode getACall() { Cached::call(this, result) }
}
cached
@@ -526,10 +556,10 @@ private module Cached {
* Holds if `func` flows to the callee of `call`.
*/
cached
predicate call(LocalSourceNode func, Node call) {
predicate call(LocalSourceNode func, CallCfgNode call) {
exists(CfgNode n |
func.flowsTo(n) and
n.asCfgNode() = call.asCfgNode().(CallNode).getFunction()
n = call.getFunction()
)
}
}
@@ -544,7 +574,12 @@ newtype TContent =
/** An element of a set. */
TSetElementContent() or
/** An element of a tuple at a specific index. */
TTupleElementContent(int index) { exists(any(TupleNode tn).getElement(index)) } or
TTupleElementContent(int index) {
exists(any(TupleNode tn).getElement(index))
or
// Arguments can overflow and end up in the starred parameter tuple.
exists(any(CallNode cn).getArg(index))
} or
/** An element of a dictionary under a specific key. */
TDictionaryElementContent(string key) {
key = any(KeyValuePair kvp).getKey().(StrConst).getS()

View File

@@ -1975,6 +1975,14 @@ private module Django {
}
}
/**
* Gets the last decorator call for the function `func`, if `func` has decorators.
*/
private Expr lastDecoratorCall(Function func) {
result = func.getDefinition().(FunctionExpr).getADecoratorCall() and
not exists(Call other_decorator | other_decorator.getArg(0) = result)
}
// ---------------------------------------------------------------------------
// routing modeling
// ---------------------------------------------------------------------------
@@ -1987,7 +1995,18 @@ private module Django {
*/
private DataFlow::Node djangoRouteHandlerFunctionTracker(DataFlow::TypeTracker t, Function func) {
t.start() and
result = DataFlow::exprNode(func.getDefinition())
(
not exists(func.getADecorator()) and
result.asExpr() = func.getDefinition()
or
// If the function has decorators, we still want to model the function as being
// the request handler for a route setup. In such situations, we must track the
// last decorator call instead of the function itself.
//
// Note that this means that we blindly ignore what the decorator actually does to
// the function, which seems like an OK tradeoff.
result.asExpr() = lastDecoratorCall(func)
)
or
exists(DataFlow::TypeTracker t2 |
result = djangoRouteHandlerFunctionTracker(t2, func).track(t2, t)
@@ -2005,18 +2024,15 @@ private module Django {
result = djangoRouteHandlerFunctionTracker(DataFlow::TypeTracker::end(), func)
}
/** A django View class defined in project code. */
class DjangoViewClassDef extends Class {
DjangoViewClassDef() { this.getABase() = django::views::generic::View::subclassRef().asExpr() }
/** Gets a function that could handle incoming requests, if any. */
DjangoRouteHandler getARequestHandler() {
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
result = this.getAMethod() and
result.getName() = HTTP::httpVerbLower()
}
/**
* In order to recognize a class as being a django view class, based on the `as_view`
* call, we need to be able to track such calls on _any_ class. This is provided by
* the member predicates of this QL class.
*
* As such, a Python class being part of `DjangoViewClassHelper` doesn't signify that
* we model it as a django view class.
*/
class DjangoViewClassHelper extends Class {
/** Gets a reference to this class. */
private DataFlow::Node getARef(DataFlow::TypeTracker t) {
t.start() and
@@ -2051,15 +2067,69 @@ private module Django {
DataFlow::Node asViewResult() { result = asViewResult(DataFlow::TypeTracker::end()) }
}
/** A class that we consider a django View class. */
abstract class DjangoViewClass extends DjangoViewClassHelper {
/** Gets a function that could handle incoming requests, if any. */
Function getARequestHandler() {
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
result = this.getAMethod() and
result.getName() = HTTP::httpVerbLower()
}
/**
* Gets a reference to instances of this class, originating from a self parameter of
* a method defined on this class.
*
* Note: TODO: This doesn't take MRO into account
* Note: TODO: This doesn't take staticmethod/classmethod into account
*/
private DataFlow::Node getASelfRef(DataFlow::TypeTracker t) {
t.start() and
result.(DataFlow::ParameterNode).getParameter() = this.getAMethod().getArg(0)
or
exists(DataFlow::TypeTracker t2 | result = this.getASelfRef(t2).track(t2, t))
}
/**
* Gets a reference to instances of this class, originating from a self parameter of
* a method defined on this class.
*
* Note: TODO: This doesn't take MRO into account
* Note: TODO: This doesn't take staticmethod/classmethod into account
*/
DataFlow::Node getASelfRef() { result = this.getASelfRef(DataFlow::TypeTracker::end()) }
}
/**
* A class that is used in a route-setup, with `<class>.as_view()`, therefore being
* considered a django View class.
*/
class DjangoViewClassFromRouteSetup extends DjangoViewClass {
DjangoViewClassFromRouteSetup() {
exists(DjangoRouteSetup setup | setup.getViewArg() = this.asViewResult())
}
}
/**
* A class that has a super-type which is a django View class, therefore also
* becoming a django View class.
*/
class DjangoViewClassFromSuperClass extends DjangoViewClass {
DjangoViewClassFromSuperClass() {
this.getABase() = django::views::generic::View::subclassRef().asExpr()
}
}
/**
* A function that is a django route handler, meaning it handles incoming requests
* with the django framework.
*/
private class DjangoRouteHandler extends Function {
DjangoRouteHandler() {
exists(djangoRouteHandlerFunctionTracker(this))
exists(DjangoRouteSetup route | route.getViewArg() = djangoRouteHandlerFunctionTracker(this))
or
any(DjangoViewClassDef vc).getARequestHandler() = this
any(DjangoViewClass vc).getARequestHandler() = this
}
/** Gets the index of the request parameter. */
@@ -2083,18 +2153,20 @@ private module Django {
final override DjangoRouteHandler getARequestHandler() {
djangoRouteHandlerFunctionTracker(result) = getViewArg()
or
exists(DjangoViewClassDef vc |
exists(DjangoViewClass vc |
getViewArg() = vc.asViewResult() and
result = vc.getARequestHandler()
)
}
override string getFramework() { result = "Django" }
}
/** A request handler defined in a django view class, that has no known route. */
private class DjangoViewClassHandlerWithoutKnownRoute extends HTTP::Server::RequestHandler::Range,
DjangoRouteHandler {
DjangoViewClassHandlerWithoutKnownRoute() {
exists(DjangoViewClassDef vc | vc.getARequestHandler() = this) and
exists(DjangoViewClass vc | vc.getARequestHandler() = this) and
not exists(DjangoRouteSetup setup | setup.getARequestHandler() = this)
}
@@ -2105,6 +2177,8 @@ private module Django {
result in [this.getArg(_), this.getArgByName(_)] and
not result = any(int i | i <= this.getRequestParamIndex() | this.getArg(i))
}
override string getFramework() { result = "Django" }
}
/**
@@ -2267,6 +2341,46 @@ private module Django {
override string getSourceType() { result = "django.http.request.HttpRequest" }
}
/**
* A read of the `request` attribute on a reference to an instance of a View class,
* which is the request being processed currently.
*
* See https://docs.djangoproject.com/en/3.1/topics/class-based-views/generic-display/#dynamic-filtering
*/
private class DjangoViewClassRequestAttributeRead extends django::http::request::HttpRequest::InstanceSource,
RemoteFlowSource::Range, DataFlow::Node {
DjangoViewClassRequestAttributeRead() {
exists(DataFlow::AttrRead read | this = read |
read.getObject() = any(DjangoViewClass vc).getASelfRef() and
read.getAttributeName() = "request"
)
}
override string getSourceType() {
result = "django.http.request.HttpRequest (attribute on self in View class)"
}
}
/**
* A read of the `args` or `kwargs` attribute on a reference to an instance of a View class,
* which contains the routed parameters captured from the URL route.
*
* See https://docs.djangoproject.com/en/3.1/topics/class-based-views/generic-display/#dynamic-filtering
*/
private class DjangoViewClassRoutedParamsAttributeRead extends RemoteFlowSource::Range,
DataFlow::Node {
DjangoViewClassRoutedParamsAttributeRead() {
exists(DataFlow::AttrRead read | this = read |
read.getObject() = any(DjangoViewClass vc).getASelfRef() and
read.getAttributeName() in ["args", "kwargs"]
)
}
override string getSourceType() {
result = "django routed param from attribute on self in View class"
}
}
private class DjangoHttpRequstAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
nodeFrom = django::http::request::HttpRequest::instance() and

View File

@@ -9,295 +9,98 @@ private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.frameworks.Werkzeug
private import semmle.python.ApiGraphs
/**
* Provides models for the `flask` PyPI package.
* See https://flask.palletsprojects.com/en/1.1.x/.
*/
private module FlaskModel {
// ---------------------------------------------------------------------------
// flask
// ---------------------------------------------------------------------------
/** Gets a reference to the `flask` module. */
private DataFlow::Node flask(DataFlow::TypeTracker t) {
t.start() and
result = DataFlow::importNode("flask")
or
exists(DataFlow::TypeTracker t2 | result = flask(t2).track(t2, t))
}
/** Gets a reference to the `flask` module. */
DataFlow::Node flask() { result = flask(DataFlow::TypeTracker::end()) }
/**
* Gets a reference to the attribute `attr_name` of the `flask` module.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node flask_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["request", "make_response", "Response", "views", "redirect"] and
(
t.start() and
result = DataFlow::importNode("flask" + "." + attr_name)
or
t.startInAttr(attr_name) and
result = flask()
)
or
// Due to bad performance when using normal setup with `flask_attr(t2, attr_name).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
flask_attr_first_join(t2, attr_name, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate flask_attr_first_join(
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(flask_attr(t2, attr_name), res, summary)
}
/**
* Gets a reference to the attribute `attr_name` of the `flask` module.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node flask_attr(string attr_name) {
result = flask_attr(DataFlow::TypeTracker::end(), attr_name)
}
/** Provides models for the `flask` module. */
module flask {
/** Gets a reference to the `flask.request` object. */
DataFlow::Node request() { result = flask_attr("request") }
/** Gets a reference to the `flask.make_response` function. */
DataFlow::Node make_response() { result = flask_attr("make_response") }
module Flask {
/** Provides models for flask view classes (defined in the `flask.views` module) */
module Views {
/**
* Provides models for the `flask.views.View` class and subclasses.
*
* See https://flask.palletsprojects.com/en/1.1.x/views/#basic-principle.
*/
module View {
/** Gets a reference to the `flask.views.View` class or any subclass. */
API::Node subclassRef() {
result =
API::moduleImport("flask")
.getMember("views")
.getMember([
"View",
// MethodView is a known subclass
"MethodView"
])
.getASubclass*()
}
}
/**
* Provides models for the `flask.Flask` class
* Provides models for the `flask.views.MethodView` class and subclasses.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.
* See https://flask.palletsprojects.com/en/1.1.x/views/#method-based-dispatching.
*/
module Flask {
/** Gets a reference to the `flask.Flask` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = DataFlow::importNode("flask.Flask")
or
t.startInAttr("Flask") and
result = flask()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `flask.Flask` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of instances of `flask.Flask`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `Flask::instance()` to get references to instances of `flask.Flask`.
*/
abstract class InstanceSource extends DataFlow::Node { }
/** A direct instantiation of `flask.Flask`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
}
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
private DataFlow::Node instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
/**
* Gets a reference to the attribute `attr_name` of an instance of `flask.Flask` (a flask application).
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node instance_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["route", "add_url_rule", "make_response"] and
t.startInAttr(attr_name) and
result = flask::Flask::instance()
or
// Due to bad performance when using normal setup with `instance_attr(t2, attr_name).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
instance_attr_first_join(t2, attr_name, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate instance_attr_first_join(
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(instance_attr(t2, attr_name), res, summary)
}
/**
* Gets a reference to the attribute `attr_name` of an instance of `flask.Flask` (a flask application).
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node instance_attr(string attr_name) {
result = instance_attr(DataFlow::TypeTracker::end(), attr_name)
}
/** Gets a reference to the `route` method on an instance of `flask.Flask`. */
DataFlow::Node route() { result = instance_attr("route") }
/** Gets a reference to the `add_url_rule` method on an instance of `flask.Flask`. */
DataFlow::Node add_url_rule() { result = instance_attr("add_url_rule") }
/** Gets a reference to the `make_response` method on an instance of `flask.Flask`. */
// HACK: We can't call this predicate `make_response` since shadowing is
// completely disallowed in QL. I added an underscore to move things forward for
// now :(
DataFlow::Node make_response_() { result = instance_attr("make_response") }
/** Gets a reference to the `response_class` attribute on the `flask.Flask` class or an instance. */
private DataFlow::Node response_class(DataFlow::TypeTracker t) {
t.startInAttr("response_class") and
result in [classRef(), instance()]
or
exists(DataFlow::TypeTracker t2 | result = response_class(t2).track(t2, t))
}
/**
* Gets a reference to the `response_class` attribute on the `flask.Flask` class or an instance.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.response_class
*/
DataFlow::Node response_class() { result = response_class(DataFlow::TypeTracker::end()) }
}
// -------------------------------------------------------------------------
// flask.views
// -------------------------------------------------------------------------
/** Gets a reference to the `flask.views` module. */
DataFlow::Node views() { result = flask_attr("views") }
/** Provides models for the `flask.views` module */
module views {
/**
* Gets a reference to the attribute `attr_name` of the `flask.views` module.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node views_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["View", "MethodView"] and
(
t.start() and
result = DataFlow::importNode("flask.views" + "." + attr_name)
or
t.startInAttr(attr_name) and
result = views()
)
or
// Due to bad performance when using normal setup with `views_attr(t2, attr_name).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
views_attr_first_join(t2, attr_name, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate views_attr_first_join(
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(views_attr(t2, attr_name), res, summary)
}
/**
* Gets a reference to the attribute `attr_name` of the `flask.views` module.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node views_attr(string attr_name) {
result = views_attr(DataFlow::TypeTracker::end(), attr_name)
}
/**
* Provides models for the `flask.views.View` class and subclasses.
*
* See https://flask.palletsprojects.com/en/1.1.x/views/#basic-principle.
*/
module View {
/** Gets a reference to the `flask.views.View` class or any subclass. */
private DataFlow::Node subclassRef(DataFlow::TypeTracker t) {
t.start() and
result = views_attr(["View", "MethodView"])
or
// subclasses in project code
result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t))
}
/** Gets a reference to the `flask.views.View` class or any subclass. */
DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `flask.views.MethodView` class and subclasses.
*
* See https://flask.palletsprojects.com/en/1.1.x/views/#method-based-dispatching.
*/
module MethodView {
/** Gets a reference to the `flask.views.View` class or any subclass. */
private DataFlow::Node subclassRef(DataFlow::TypeTracker t) {
t.start() and
result = views_attr("MethodView")
or
// subclasses in project code
result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t))
}
/** Gets a reference to the `flask.views.View` class or any subclass. */
DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
module MethodView {
/** Gets a reference to the `flask.views.MethodView` class or any subclass. */
API::Node subclassRef() {
result =
API::moduleImport("flask").getMember("views").getMember("MethodView").getASubclass*()
}
}
}
/**
* Provides models for flask applications (instances of the `flask.Flask` class).
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.
*/
module FlaskApp {
/** Gets a reference to the `flask.Flask` class. */
API::Node classRef() { result = API::moduleImport("flask").getMember("Flask") }
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
API::Node instance() { result = classRef().getReturn() }
}
/**
* Provides models for flask blueprints (instances of the `flask.Blueprint` class).
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Blueprint.
*/
module Blueprint {
/** Gets a reference to the `flask.Blueprint` class. */
API::Node classRef() { result = API::moduleImport("flask").getMember("Blueprint") }
/** Gets a reference to an instance of `flask.Blueprint`. */
API::Node instance() { result = classRef().getReturn() }
}
/** Gets a reference to the `flask.request` object. */
API::Node request() { result = API::moduleImport("flask").getMember("request") }
/**
* Provides models for the `flask.Response` class
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Response.
*/
module Response {
/** Gets a reference to the `flask.Response` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result in [flask_attr("Response"), flask::Flask::response_class()]
/**
* Gets a reference to the `flask.Response` class, possibly through the
* `response_class` class attribute on a flask application (which by is an alias for
* `flask.Response` by default).
*/
API::Node classRef() {
result = API::moduleImport("flask").getMember("Response")
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
result = [FlaskApp::classRef(), FlaskApp::instance()].getMember("response_class")
}
/** Gets a reference to the `flask.Response` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* INTERNAL: Do not use.
*
* A source of instances of `flask.Response`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
@@ -309,23 +112,21 @@ private module FlaskModel {
abstract class InstanceSource extends HTTP::Server::HttpResponse::Range, DataFlow::Node { }
/** A direct instantiation of `flask.Response`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
ClassInstantiation() { this = classRef().getACall() }
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() { result.asCfgNode() = node.getArg(0) }
override DataFlow::Node getBody() { result = this.getArg(0) }
override string getMimetypeDefault() { result = "text/html" }
/** Gets the argument passed to the `mimetype` parameter, if any. */
private DataFlow::Node getMimetypeArg() {
result.asCfgNode() in [node.getArg(3), node.getArgByName("mimetype")]
result in [this.getArg(3), this.getArgByName("mimetype")]
}
/** Gets the argument passed to the `content_type` parameter, if any. */
private DataFlow::Node getContentTypeArg() {
result.asCfgNode() in [node.getArg(4), node.getArgByName("content_type")]
result in [this.getArg(4), this.getArgByName("content_type")]
}
override DataFlow::Node getMimetypeOrContentTypeArg() {
@@ -337,8 +138,32 @@ private module FlaskModel {
}
}
/**
* A call to either `flask.make_response` function, or the `make_response` method on
* an instance of `flask.Flask`. This creates an instance of the `flask_response`
* class (class-attribute on a flask application), which by default is
* `flask.Response`.
*
* See
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
*/
private class FlaskMakeResponseCall extends InstanceSource, DataFlow::CallCfgNode {
FlaskMakeResponseCall() {
this = API::moduleImport("flask").getMember("make_response").getACall()
or
this = FlaskApp::instance().getMember("make_response").getACall()
}
override DataFlow::Node getBody() { result = this.getArg(0) }
override string getMimetypeDefault() { result = "text/html" }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}
/** Gets a reference to an instance of `flask.Response`. */
private DataFlow::Node instance(DataFlow::TypeTracker t) {
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
@@ -346,15 +171,23 @@ private module FlaskModel {
}
/** Gets a reference to an instance of `flask.Response`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
}
// ---------------------------------------------------------------------------
// routing modeling
// ---------------------------------------------------------------------------
/** A flask View class defined in project code. */
class FlaskViewClassDef extends Class {
FlaskViewClassDef() { this.getABase() = flask::views::View::subclassRef().asExpr() }
/**
* A class that is a subclass of the `flask.views.View` class,
* thereby being able to handle incoming HTTP requests.
*/
class FlaskViewClass extends Class {
API::Node api_node;
FlaskViewClass() {
this.getABase() = Views::View::subclassRef().getAUse().asExpr() and
api_node.getAnImmediateUse().asExpr().(ClassExpr) = this.getParent()
}
/** Gets a function that could handle incoming requests, if any. */
Function getARequestHandler() {
@@ -364,42 +197,22 @@ private module FlaskModel {
result.getName() = "dispatch_request"
}
/** Gets a reference to this class. */
private DataFlow::Node getARef(DataFlow::TypeTracker t) {
t.start() and
result.asExpr().(ClassExpr) = this.getParent()
or
exists(DataFlow::TypeTracker t2 | result = this.getARef(t2).track(t2, t))
}
/** Gets a reference to this class. */
DataFlow::Node getARef() { result = this.getARef(DataFlow::TypeTracker::end()) }
/** Gets a reference to the `as_view` classmethod of this class. */
private DataFlow::Node asViewRef(DataFlow::TypeTracker t) {
t.startInAttr("as_view") and
result = this.getARef()
or
exists(DataFlow::TypeTracker t2 | result = this.asViewRef(t2).track(t2, t))
}
/** Gets a reference to the `as_view` classmethod of this class. */
DataFlow::Node asViewRef() { result = this.asViewRef(DataFlow::TypeTracker::end()) }
/** Gets a reference to the result of calling the `as_view` classmethod of this class. */
private DataFlow::Node asViewResult(DataFlow::TypeTracker t) {
t.start() and
result.asCfgNode().(CallNode).getFunction() = this.asViewRef().asCfgNode()
or
exists(DataFlow::TypeTracker t2 | result = asViewResult(t2).track(t2, t))
}
/** Gets a reference to the result of calling the `as_view` classmethod of this class. */
DataFlow::Node asViewResult() { result = asViewResult(DataFlow::TypeTracker::end()) }
/**
* INTERNAL: Do not use.
* Gets a reference to the result of calling the `as_view` classmethod of this class.
*/
API::Node asViewResult() { result = api_node.getMember("as_view").getReturn() }
}
class FlaskMethodViewClassDef extends FlaskViewClassDef {
FlaskMethodViewClassDef() { this.getABase() = flask::views::MethodView::subclassRef().asExpr() }
/**
* A class that is a subclass of the `flask.views.MethodView` class.
* thereby being able to handle incoming HTTP requests.
*/
class FlaskMethodViewClass extends FlaskViewClass {
FlaskMethodViewClass() {
this.getABase() = Views::MethodView::subclassRef().getAUse().asExpr() and
api_node.getAnImmediateUse().asExpr().(ClassExpr) = this.getParent()
}
override Function getARequestHandler() {
result = super.getARequestHandler()
@@ -435,42 +248,46 @@ private module FlaskModel {
)
)
}
override string getFramework() { result = "Flask" }
}
/**
* A call to the `route` method on an instance of `flask.Flask`.
* A call to the `route` method on an instance of `flask.Flask` or an instance of `flask.Blueprint`.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.route
*/
private class FlaskAppRouteCall extends FlaskRouteSetup, DataFlow::CfgNode {
override CallNode node;
FlaskAppRouteCall() { node.getFunction() = flask::Flask::route().asCfgNode() }
private class FlaskAppRouteCall extends FlaskRouteSetup, DataFlow::CallCfgNode {
FlaskAppRouteCall() {
this = FlaskApp::instance().getMember("route").getACall()
or
this = Blueprint::instance().getMember("route").getACall()
}
override DataFlow::Node getUrlPatternArg() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("rule")]
result in [this.getArg(0), this.getArgByName("rule")]
}
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
}
/**
* A call to the `add_url_rule` method on an instance of `flask.Flask`.
* A call to the `add_url_rule` method on an instance of `flask.Flask` or an instance of `flask.Blueprint`.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.add_url_rule
*/
private class FlaskAppAddUrlRuleCall extends FlaskRouteSetup, DataFlow::CfgNode {
override CallNode node;
FlaskAppAddUrlRuleCall() { node.getFunction() = flask::Flask::add_url_rule().asCfgNode() }
private class FlaskAppAddUrlRuleCall extends FlaskRouteSetup, DataFlow::CallCfgNode {
FlaskAppAddUrlRuleCall() {
this = FlaskApp::instance().getMember("add_url_rule").getACall()
or
this = Blueprint::instance().getMember("add_url_rule").getACall()
}
override DataFlow::Node getUrlPatternArg() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("rule")]
result in [this.getArg(0), this.getArgByName("rule")]
}
DataFlow::Node getViewArg() {
result.asCfgNode() in [node.getArg(2), node.getArgByName("view_func")]
}
DataFlow::Node getViewArg() { result in [this.getArg(2), this.getArgByName("view_func")] }
override Function getARequestHandler() {
exists(DataFlow::LocalSourceNode func_src |
@@ -478,8 +295,8 @@ private module FlaskModel {
func_src.asExpr().(CallableExpr) = result.getDefinition()
)
or
exists(FlaskViewClassDef vc |
getViewArg() = vc.asViewResult() and
exists(FlaskViewClass vc |
getViewArg() = vc.asViewResult().getAUse() and
result = vc.getARequestHandler()
)
}
@@ -488,7 +305,7 @@ private module FlaskModel {
/** A request handler defined in a django view class, that has no known route. */
private class FlaskViewClassHandlerWithoutKnownRoute extends HTTP::Server::RequestHandler::Range {
FlaskViewClassHandlerWithoutKnownRoute() {
exists(FlaskViewClassDef vc | vc.getARequestHandler() = this) and
exists(FlaskViewClass vc | vc.getARequestHandler() = this) and
not exists(FlaskRouteSetup setup | setup.getARequestHandler() = this)
}
@@ -499,70 +316,52 @@ private module FlaskModel {
result in [this.getArg(_), this.getArgByName(_)] and
not result = this.getArg(0)
}
override string getFramework() { result = "Flask" }
}
// ---------------------------------------------------------------------------
// flask.Request taint modeling
// ---------------------------------------------------------------------------
// TODO: Do we even need this class? :|
/**
* A source of remote flow from a flask request.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
*/
private class RequestSource extends RemoteFlowSource::Range {
RequestSource() { this = flask::request() }
private class FlaskRequestSource extends RemoteFlowSource::Range {
FlaskRequestSource() {
this = request().getAUse() and
not any(Import imp).contains(this.asExpr()) and
not exists(ControlFlowNode def | this.asVar().getSourceVariable().hasDefiningNode(def) |
any(Import imp).contains(def.getNode())
)
}
override string getSourceType() { result = "flask.request" }
}
private module FlaskRequestTracking {
/** Gets a reference to the `get_data` attribute of a Flask request. */
private DataFlow::Node get_data(DataFlow::TypeTracker t) {
t.startInAttr("get_data") and
result = flask::request()
or
exists(DataFlow::TypeTracker t2 | result = get_data(t2).track(t2, t))
}
/** Gets a reference to the `get_data` attribute of a Flask request. */
DataFlow::Node get_data() { result = get_data(DataFlow::TypeTracker::end()) }
/** Gets a reference to the `get_json` attribute of a Flask request. */
private DataFlow::Node get_json(DataFlow::TypeTracker t) {
t.startInAttr("get_json") and
result = flask::request()
or
exists(DataFlow::TypeTracker t2 | result = get_json(t2).track(t2, t))
}
/** Gets a reference to the `get_json` attribute of a Flask request. */
DataFlow::Node get_json() { result = get_json(DataFlow::TypeTracker::end()) }
/** Gets a reference to either of the `get_json` or `get_data` attributes of a Flask request. */
DataFlow::Node tainted_methods(string attr_name) {
result = get_data() and
attr_name = "get_data"
or
result = get_json() and
attr_name = "get_json"
}
}
/**
* A source of remote flow from attributes from a flask request.
* Taint propagation for a flask request.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
*/
private class RequestInputAccess extends RemoteFlowSource::Range {
string attr_name;
RequestInputAccess() {
// attributes
exists(AttrNode attr |
this.asCfgNode() = attr and attr.getObject(attr_name) = flask::request().asCfgNode()
|
attr_name in [
private class FlaskRequestAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Methods
exists(string method_name | method_name in ["get_data", "get_json"] |
// Method access
nodeFrom = request().getAUse() and
nodeTo = request().getMember(method_name).getAnImmediateUse()
or
// Method call
nodeFrom = request().getMember(method_name).getAUse() and
nodeTo.(DataFlow::CallCfgNode).getFunction() = nodeFrom
)
or
// Attributes
nodeFrom = request().getAUse() and
exists(DataFlow::AttrRead read | nodeTo = read and read.getObject() = nodeFrom |
read.getAttributeName() in [
// str
"path", "full_path", "base_url", "url", "access_control_request_method",
"content_encoding", "content_md5", "content_type", "data", "method", "mimetype",
@@ -598,63 +397,29 @@ private module FlaskModel {
"headers"
]
)
or
// methods (needs special handling to track bound-methods -- see `FlaskRequestMethodCallsAdditionalTaintStep` below)
this = FlaskRequestTracking::tainted_methods(attr_name)
}
override string getSourceType() { result = "flask.request input" }
}
private class FlaskRequestMethodCallsAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// NOTE: `request -> request.tainted_method` part is handled as part of RequestInputAccess
// tainted_method -> tainted_method()
nodeFrom = FlaskRequestTracking::tainted_methods(_) and
nodeTo.asCfgNode().(CallNode).getFunction() = nodeFrom.asCfgNode()
}
}
private class RequestInputMultiDict extends RequestInputAccess,
Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
RequestInputMultiDict() { attr_name in ["args", "values", "form", "files"] }
private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
string attr_name;
RequestAttrMultiDict() {
attr_name in ["args", "values", "form", "files"] and
this = request().getMember(attr_name).getAnImmediateUse()
}
}
private class RequestInputFiles extends RequestInputMultiDict {
RequestInputFiles() { attr_name = "files" }
private class RequestAttrFiles extends RequestAttrMultiDict {
// TODO: Somehow specify that elements of `RequestAttrFiles` are
// Werkzeug::werkzeug::datastructures::FileStorage and should have those additional taint steps
// AND that the 0-indexed argument to its' save method is a sink for path-injection.
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.save
RequestAttrFiles() { attr_name = "files" }
}
// TODO: Somehow specify that elements of `RequestInputFiles` are
// Werkzeug::werkzeug::datastructures::FileStorage and should have those additional taint steps
// AND that the 0-indexed argument to its' save method is a sink for path-injection.
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.save
// ---------------------------------------------------------------------------
// Response modeling
// Implicit response from returns of flask request handlers
// ---------------------------------------------------------------------------
/**
* A call to either `flask.make_response` function, or the `make_response` method on
* an instance of `flask.Flask`.
*
* See
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
*/
private class FlaskMakeResponseCall extends HTTP::Server::HttpResponse::Range, DataFlow::CfgNode {
override CallNode node;
FlaskMakeResponseCall() {
node.getFunction() = flask::make_response().asCfgNode()
or
node.getFunction() = flask::Flask::make_response_().asCfgNode()
}
override DataFlow::Node getBody() { result.asCfgNode() = node.getArg(0) }
override string getMimetypeDefault() { result = "text/html" }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}
private class FlaskRouteHandlerReturn extends HTTP::Server::HttpResponse::Range, DataFlow::CfgNode {
FlaskRouteHandlerReturn() {
exists(Function routeHandler |
@@ -670,19 +435,20 @@ private module FlaskModel {
override string getMimetypeDefault() { result = "text/html" }
}
// ---------------------------------------------------------------------------
// flask.redirect
// ---------------------------------------------------------------------------
/**
* A call to the `flask.redirect` function.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.redirect
*/
private class FlaskRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
DataFlow::CfgNode {
override CallNode node;
FlaskRedirectCall() { node.getFunction() = flask_attr("redirect").asCfgNode() }
DataFlow::CallCfgNode {
FlaskRedirectCall() { this = API::moduleImport("flask").getMember("redirect").getACall() }
override DataFlow::Node getRedirectLocation() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("location")]
result in [this.getArg(0), this.getArgByName("location")]
}
override DataFlow::Node getBody() { none() }

View File

@@ -1630,6 +1630,8 @@ private module Stdlib {
}
override Parameter getARoutedParameter() { none() }
override string getFramework() { result = "Stdlib" }
}
}

View File

@@ -486,7 +486,9 @@ private module Tornado {
}
/** A tornado route setup. */
abstract class TornadoRouteSetup extends HTTP::Server::RouteSetup::Range { }
abstract class TornadoRouteSetup extends HTTP::Server::RouteSetup::Range {
override string getFramework() { result = "Tornado" }
}
/**
* A regex that is used to set up a route.
@@ -561,6 +563,8 @@ private module Tornado {
result in [this.getArg(_), this.getArgByName(_)] and
not result = this.getArg(0)
}
override string getFramework() { result = "Tornado" }
}
// ---------------------------------------------------------------------------

View File

@@ -29,7 +29,13 @@ private module Yaml {
* For example, using `attr_name = "load"` will get all uses of `yaml.load`.
*/
private DataFlow::Node yaml_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["load", "SafeLoader", "BaseLoader"] and
attr_name in [
// functions
"load", "load_all", "full_load", "full_load_all", "unsafe_load", "unsafe_load_all",
"safe_load", "safe_load_all",
// Classes
"SafeLoader", "BaseLoader"
] and
(
t.start() and
result = DataFlow::importNode("yaml." + attr_name)
@@ -68,13 +74,22 @@ private module Yaml {
}
/**
* A call to `yaml.load`
* A call to any of the loading functions in `yaml` (`load`, `load_all`, `full_load`,
* `full_load_all`, `unsafe_load`, `unsafe_load_all`, `safe_load`, `safe_load_all`)
*
* See https://pyyaml.org/wiki/PyYAMLDocumentation (you will have to scroll down).
*/
private class YamlLoadCall extends Decoding::Range, DataFlow::CfgNode {
override CallNode node;
string func_name;
YamlLoadCall() { node.getFunction() = Yaml::yaml::yaml_attr("load").asCfgNode() }
YamlLoadCall() {
func_name in [
"load", "load_all", "full_load", "full_load_all", "unsafe_load", "unsafe_load_all",
"safe_load", "safe_load_all"
] and
node.getFunction() = Yaml::yaml::yaml_attr(func_name).asCfgNode()
}
/**
* This function was thought safe from the 5.1 release in 2017, when the default loader was changed to `FullLoader`.
@@ -84,10 +99,16 @@ private class YamlLoadCall extends Decoding::Range, DataFlow::CfgNode {
* See https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation for more details.
*/
override predicate mayExecuteInput() {
func_name in ["full_load", "full_load_all", "unsafe_load", "unsafe_load_all"]
or
func_name in ["load", "load_all"] and
// If the `Loader` is not set to either `SafeLoader` or `BaseLoader` or not set at all,
// then the default loader will be used, which is not safe.
not node.getArgByName("Loader") =
Yaml::yaml::yaml_attr(["SafeLoader", "BaseLoader"]).asCfgNode()
not exists(DataFlow::Node loader_arg |
loader_arg.asCfgNode() in [node.getArg(1), node.getArgByName("Loader")]
|
loader_arg = Yaml::yaml::yaml_attr(["SafeLoader", "BaseLoader"])
)
}
override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) }