mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
Merge branch 'main' into crypto
This commit is contained in:
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -1508,6 +1508,8 @@ predicate forReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
|
||||
c instanceof ListElementContent
|
||||
or
|
||||
c instanceof SetElementContent
|
||||
or
|
||||
c instanceof TupleElementContent
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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. */
|
||||
@@ -561,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()
|
||||
|
||||
@@ -15,129 +15,92 @@ 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. */
|
||||
API::Node flask() { result = API::moduleImport("flask") }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `flask` module.
|
||||
*/
|
||||
private API::Node flask_attr(string attr_name) { result = flask().getMember(attr_name) }
|
||||
|
||||
/** Provides models for the `flask` module. */
|
||||
module flask {
|
||||
/** Gets a reference to the `flask.request` object. */
|
||||
API::Node request() { result = flask_attr("request") }
|
||||
|
||||
/** Gets a reference to the `flask.make_response` function. */
|
||||
API::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.Flask` class
|
||||
* Provides models for the `flask.views.View` 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/#basic-principle.
|
||||
*/
|
||||
module Flask {
|
||||
/** Gets a reference to the `flask.Flask` class. */
|
||||
API::Node classRef() { result = flask().getMember("Flask") }
|
||||
|
||||
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
|
||||
API::Node instance() { result = classRef().getReturn() }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of an instance of `flask.Flask` (a flask application).
|
||||
*/
|
||||
private API::Node instance_attr(string attr_name) { result = instance().getMember(attr_name) }
|
||||
|
||||
/** Gets a reference to the `route` method on an instance of `flask.Flask`. */
|
||||
API::Node route() { result = instance_attr("route") }
|
||||
|
||||
/** Gets a reference to the `add_url_rule` method on an instance of `flask.Flask`. */
|
||||
API::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 :(
|
||||
API::Node make_response_() { result = instance_attr("make_response") }
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
API::Node response_class() { result = [classRef(), instance()].getMember("response_class") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for 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 = flask().getMember("Blueprint") }
|
||||
|
||||
/** Gets a reference to an instance of `flask.Blueprint`. */
|
||||
API::Node instance() { result = classRef().getReturn() }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of an instance of `flask.Blueprint`.
|
||||
*/
|
||||
private API::Node instance_attr(string attr_name) { result = instance().getMember(attr_name) }
|
||||
|
||||
/** Gets a reference to the `route` method on an instance of `flask.Blueprint`. */
|
||||
API::Node route() { result = instance_attr("route") }
|
||||
|
||||
/** Gets a reference to the `add_url_rule` method on an instance of `flask.Blueprint`. */
|
||||
API::Node add_url_rule() { result = instance_attr("add_url_rule") }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// flask.views
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `flask.views` module. */
|
||||
API::Node views() { result = flask_attr("views") }
|
||||
|
||||
/** Provides models for 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 = views().getMember(["View", "MethodView"]).getASubclass*()
|
||||
}
|
||||
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.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.MethodView` class or any subclass. */
|
||||
API::Node subclassRef() { result = views().getMember("MethodView").getASubclass*() }
|
||||
/**
|
||||
* 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.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. */
|
||||
API::Node classRef() { result = [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
|
||||
result = [FlaskApp::classRef(), FlaskApp::instance()].getMember("response_class")
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -175,19 +138,54 @@ 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`. */
|
||||
API::Node instance() { result = classRef().getReturn() }
|
||||
private DataFlow::LocalSourceNode 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.Response`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// routing modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/** A flask View class defined in project code. */
|
||||
class FlaskViewClassDef extends Class {
|
||||
/**
|
||||
* 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;
|
||||
|
||||
FlaskViewClassDef() {
|
||||
this.getABase() = flask::views::View::subclassRef().getAUse().asExpr() and
|
||||
FlaskViewClass() {
|
||||
this.getABase() = Views::View::subclassRef().getAUse().asExpr() and
|
||||
api_node.getAnImmediateUse().asExpr().(ClassExpr) = this.getParent()
|
||||
}
|
||||
|
||||
@@ -199,13 +197,20 @@ private module FlaskModel {
|
||||
result.getName() = "dispatch_request"
|
||||
}
|
||||
|
||||
/** Gets a reference to the result of calling the `as_view` classmethod of this class. */
|
||||
/**
|
||||
* 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().getAUse().asExpr() and
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
@@ -254,9 +259,9 @@ private module FlaskModel {
|
||||
*/
|
||||
private class FlaskAppRouteCall extends FlaskRouteSetup, DataFlow::CallCfgNode {
|
||||
FlaskAppRouteCall() {
|
||||
this.getFunction() = flask::Flask::route().getAUse()
|
||||
this = FlaskApp::instance().getMember("route").getACall()
|
||||
or
|
||||
this.getFunction() = flask::Blueprint::route().getAUse()
|
||||
this = Blueprint::instance().getMember("route").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
@@ -273,9 +278,9 @@ private module FlaskModel {
|
||||
*/
|
||||
private class FlaskAppAddUrlRuleCall extends FlaskRouteSetup, DataFlow::CallCfgNode {
|
||||
FlaskAppAddUrlRuleCall() {
|
||||
this.getFunction() = flask::Flask::add_url_rule().getAUse()
|
||||
this = FlaskApp::instance().getMember("add_url_rule").getACall()
|
||||
or
|
||||
this.getFunction() = flask::Blueprint::add_url_rule().getAUse()
|
||||
this = Blueprint::instance().getMember("add_url_rule").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
@@ -290,7 +295,7 @@ private module FlaskModel {
|
||||
func_src.asExpr().(CallableExpr) = result.getDefinition()
|
||||
)
|
||||
or
|
||||
exists(FlaskViewClassDef vc |
|
||||
exists(FlaskViewClass vc |
|
||||
getViewArg() = vc.asViewResult().getAUse() and
|
||||
result = vc.getARequestHandler()
|
||||
)
|
||||
@@ -300,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)
|
||||
}
|
||||
|
||||
@@ -318,128 +323,103 @@ private module FlaskModel {
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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().getAUse() }
|
||||
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 either of the `get_json` or `get_data` attributes of a Flask request. */
|
||||
API::Node tainted_methods(string attr_name) {
|
||||
attr_name in ["get_data", "get_json"] and
|
||||
result = flask::request().getMember(attr_name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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",
|
||||
"origin", "query_string", "referrer", "remote_addr", "remote_user", "user_agent",
|
||||
// dict
|
||||
"environ", "cookies", "mimetype_params", "view_args",
|
||||
// json
|
||||
"json",
|
||||
// List[str]
|
||||
"access_route",
|
||||
// file-like
|
||||
"stream", "input_stream",
|
||||
// MultiDict[str, str]
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict
|
||||
"args", "values", "form",
|
||||
// MultiDict[str, FileStorage]
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage
|
||||
// TODO: FileStorage needs extra taint steps
|
||||
"files",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.HeaderSet
|
||||
"access_control_request_headers", "pragma",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Accept
|
||||
// TODO: Kinda badly modeled for now -- has type List[Tuple[value, quality]], and some extra methods
|
||||
"accept_charsets", "accept_encodings", "accept_languages", "accept_mimetypes",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Authorization
|
||||
// TODO: dict subclass with extra attributes like `username` and `password`
|
||||
"authorization",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.RequestCacheControl
|
||||
// TODO: has attributes like `no_cache`, and `to_header` method (actually, many of these models do)
|
||||
"cache_control",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers
|
||||
// TODO: dict-like with wsgiref.headers.Header compatibility methods
|
||||
"headers"
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
|
||||
string attr_name;
|
||||
|
||||
RequestInputAccess() {
|
||||
// attributes
|
||||
this = flask::request().getMember(attr_name).getAnImmediateUse() and
|
||||
attr_name in [
|
||||
// str
|
||||
"path", "full_path", "base_url", "url", "access_control_request_method",
|
||||
"content_encoding", "content_md5", "content_type", "data", "method", "mimetype", "origin",
|
||||
"query_string", "referrer", "remote_addr", "remote_user", "user_agent",
|
||||
// dict
|
||||
"environ", "cookies", "mimetype_params", "view_args",
|
||||
// json
|
||||
"json",
|
||||
// List[str]
|
||||
"access_route",
|
||||
// file-like
|
||||
"stream", "input_stream",
|
||||
// MultiDict[str, str]
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict
|
||||
"args", "values", "form",
|
||||
// MultiDict[str, FileStorage]
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage
|
||||
// TODO: FileStorage needs extra taint steps
|
||||
"files",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.HeaderSet
|
||||
"access_control_request_headers", "pragma",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Accept
|
||||
// TODO: Kinda badly modeled for now -- has type List[Tuple[value, quality]], and some extra methods
|
||||
"accept_charsets", "accept_encodings", "accept_languages", "accept_mimetypes",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Authorization
|
||||
// TODO: dict subclass with extra attributes like `username` and `password`
|
||||
"authorization",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.RequestCacheControl
|
||||
// TODO: has attributes like `no_cache`, and `to_header` method (actually, many of these models do)
|
||||
"cache_control",
|
||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers
|
||||
// TODO: dict-like with wsgiref.headers.Header compatibility methods
|
||||
"headers"
|
||||
]
|
||||
or
|
||||
// methods (needs special handling to track bound-methods -- see `FlaskRequestMethodCallsAdditionalTaintStep` below)
|
||||
this = FlaskRequestTracking::tainted_methods(attr_name).getAUse()
|
||||
}
|
||||
|
||||
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(_).getAUse() and
|
||||
nodeTo.(DataFlow::CallCfgNode).getFunction() = nodeFrom
|
||||
RequestAttrMultiDict() {
|
||||
attr_name in ["args", "values", "form", "files"] and
|
||||
this = request().getMember(attr_name).getAnImmediateUse()
|
||||
}
|
||||
}
|
||||
|
||||
private class RequestInputMultiDict extends RequestInputAccess,
|
||||
Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
|
||||
RequestInputMultiDict() { attr_name in ["args", "values", "form", "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" }
|
||||
}
|
||||
|
||||
private class RequestInputFiles extends RequestInputMultiDict {
|
||||
RequestInputFiles() { 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::CallCfgNode {
|
||||
FlaskMakeResponseCall() {
|
||||
this.getFunction() = flask::make_response().getAUse()
|
||||
or
|
||||
this.getFunction() = flask::Flask::make_response_().getAUse()
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this.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 |
|
||||
@@ -455,6 +435,9 @@ private module FlaskModel {
|
||||
override string getMimetypeDefault() { result = "text/html" }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// flask.redirect
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* A call to the `flask.redirect` function.
|
||||
*
|
||||
@@ -462,7 +445,7 @@ private module FlaskModel {
|
||||
*/
|
||||
private class FlaskRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
FlaskRedirectCall() { this.getFunction() = flask_attr("redirect").getAUse() }
|
||||
FlaskRedirectCall() { this = API::moduleImport("flask").getMember("redirect").getACall() }
|
||||
|
||||
override DataFlow::Node getRedirectLocation() {
|
||||
result in [this.getArg(0), this.getArgByName("location")]
|
||||
|
||||
@@ -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) }
|
||||
|
||||
Reference in New Issue
Block a user