Merge pull request #4415 from RasmusWL/python-flask-routed-parameter

Python: Add support for routed parameters in flask
This commit is contained in:
Taus
2020-10-14 13:29:51 +02:00
committed by GitHub
10 changed files with 462 additions and 118 deletions

View File

@@ -2,6 +2,7 @@ private import python
private import experimental.dataflow.DataFlow
// Need to import since frameworks can extend `RemoteFlowSource::Range`
private import experimental.semmle.python.Frameworks
private import experimental.semmle.python.Concepts
/**
* A data flow source of remote user input.

View File

@@ -7,6 +7,7 @@
import python
private import experimental.dataflow.DataFlow
private import experimental.semmle.python.Frameworks
private import experimental.dataflow.RemoteFlowSources
/**
* A data-flow node that executes an operating system command,
@@ -38,3 +39,62 @@ module SystemCommandExecution {
abstract DataFlow::Node getCommand();
}
}
/** Provides classes for modeling HTTP-related APIs. */
module HTTP {
/** Provides classes for modeling HTTP servers. */
module Server {
/**
* An data-flow node that sets up a route on a server.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `RouteSetup::Range` instead.
*/
class RouteSetup extends DataFlow::Node {
RouteSetup::Range range;
RouteSetup() { this = range }
/** Gets the URL pattern for this route, if it can be statically determined. */
string getUrlPattern() { result = range.getUrlPattern() }
/** Gets a function that will handle incoming requests for this route, if any. */
Function getARouteHandler() { result = range.getARouteHandler() }
/**
* Gets a parameter that will receive parts of the url when handling incoming
* requests for this route, if any. These automatically become a `RemoteFlowSource`.
*/
Parameter getARoutedParameter() { result = range.getARoutedParameter() }
}
/** Provides a class for modeling new HTTP routing APIs. */
module RouteSetup {
/**
* An data-flow node that sets up a route on a server.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `RouteSetup` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the URL pattern for this route, if it can be statically determined. */
abstract string getUrlPattern();
/** Gets a function that will handle incoming requests for this route, if any. */
abstract Function getARouteHandler();
/**
* Gets a parameter that will receive parts of the url when handling incoming
* requests for this route, if any. These automatically become a `RemoteFlowSource`.
*/
abstract Parameter getARoutedParameter();
}
}
private class RoutedParameter extends RemoteFlowSource::Range, DataFlow::ParameterNode {
RoutedParameter() { this.getParameter() = any(RouteSetup setup).getARoutedParameter() }
override string getSourceType() { result = "RoutedParameter" }
}
}
}

View File

@@ -1,5 +1,6 @@
/**
* Provides classes modeling security-relevant aspects of the `flask` package.
* Provides classes modeling security-relevant aspects of the `flask` PyPI package.
* See https://flask.palletsprojects.com/en/1.1.x/.
*/
private import python
@@ -11,6 +12,10 @@ private import experimental.semmle.python.frameworks.Werkzeug
// for old improved impl see
// https://github.com/github/codeql/blob/9f95212e103c68d0c1dfa4b6f30fb5d53954ccef/python/ql/src/semmle/python/web/flask/Request.qll
/**
* Provides models for the `flask` PyPI package.
* See https://flask.palletsprojects.com/en/1.1.x/.
*/
private module Flask {
/** Gets a reference to the `flask` module. */
DataFlow::Node flask(DataFlow::TypeTracker t) {
@@ -23,6 +28,7 @@ private module Flask {
/** Gets a reference to the `flask` module. */
DataFlow::Node flask() { result = flask(DataFlow::TypeTracker::end()) }
/** Provides models for the `flask` module. */
module flask {
/** Gets a reference to the `flask.request` object. */
DataFlow::Node request(DataFlow::TypeTracker t) {
@@ -32,13 +38,154 @@ private module Flask {
t.startInAttr("request") and
result = flask()
or
exists(DataFlow::TypeTracker t2 | result = flask::request(t2).track(t2, t))
exists(DataFlow::TypeTracker t2 | result = request(t2).track(t2, t))
}
/** Gets a reference to the `flask.request` object. */
DataFlow::Node request() { result = flask::request(DataFlow::TypeTracker::end()) }
DataFlow::Node request() { result = request(DataFlow::TypeTracker::end()) }
/** Gets a reference to the `flask.Flask` class. */
private DataFlow::Node classFlask(DataFlow::TypeTracker t) {
t.start() and
result = DataFlow::importNode("flask.Flask")
or
t.startInAttr("Flask") and
result = flask()
or
exists(DataFlow::TypeTracker t2 | result = classFlask(t2).track(t2, t))
}
/** Gets a reference to the `flask.Flask` class. */
DataFlow::Node classFlask() { result = classFlask(DataFlow::TypeTracker::end()) }
/** Gets a reference to an instance of `flask.Flask` (a Flask application). */
private DataFlow::Node app(DataFlow::TypeTracker t) {
t.start() and
result.asCfgNode().(CallNode).getFunction() = flask::classFlask().asCfgNode()
or
exists(DataFlow::TypeTracker t2 | result = app(t2).track(t2, t))
}
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
DataFlow::Node app() { result = app(DataFlow::TypeTracker::end()) }
}
// ---------------------------------------------------------------------------
// routing modeling
// ---------------------------------------------------------------------------
/**
* Gets a reference to the attribute `attr_name` of a flask application.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node app_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["route", "add_url_rule"] and
t.startInAttr(attr_name) and
result = flask::app()
or
// Due to bad performance when using normal setup with `app_attr(t2, attr_name).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
app_attr_first_join(t2, attr_name, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate app_attr_first_join(
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(app_attr(t2, attr_name), res, summary)
}
/**
* Gets a reference to the attribute `attr_name` of a flask application.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node app_attr(string attr_name) {
result = app_attr(DataFlow::TypeTracker::end(), attr_name)
}
private string werkzeug_rule_re() {
// since flask uses werkzeug internally, we are using its routing rules from
// https://github.com/pallets/werkzeug/blob/4dc8d6ab840d4b78cbd5789cef91b01e3bde01d5/src/werkzeug/routing.py#L138-L151
result =
"(?<static>[^<]*)<(?:(?<converter>[a-zA-Z_][a-zA-Z0-9_]*)(?:\\((?<args>.*?)\\))?\\:)?(?<variable>[a-zA-Z_][a-zA-Z0-9_]*)>"
}
/** A route setup made by flask (sharing handling of URL patterns). */
abstract private class FlaskRouteSetup extends HTTP::Server::RouteSetup::Range {
override Parameter getARoutedParameter() {
// If we don't know the URL pattern, we simply mark all parameters as a routed
// parameter. This should give us more RemoteFlowSources but could also lead to
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
not exists(this.getUrlPattern()) and
result = this.getARouteHandler().getArgByName(_)
or
exists(string name |
result = this.getARouteHandler().getArgByName(name) and
exists(string match |
match = this.getUrlPattern().regexpFind(werkzeug_rule_re(), _, _) and
name = match.regexpCapture(werkzeug_rule_re(), 4)
)
)
}
/** Gets the argument used to pass in the URL pattern. */
abstract DataFlow::Node getUrlPatternArg();
override string getUrlPattern() {
exists(StrConst str |
DataFlow::localFlow(DataFlow::exprNode(str), this.getUrlPatternArg()) and
result = str.getText()
)
}
}
/**
* A call to `flask.Flask.route`.
*
* 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() = app_attr("route").asCfgNode() }
override DataFlow::Node getUrlPatternArg() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("rule")]
}
override Function getARouteHandler() { result.getADecorator().getAFlowNode() = node }
}
/**
* A call to `flask.Flask.add_url_rule`.
*
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.add_url_rule
*/
private class FlaskAppAddUrlRule extends FlaskRouteSetup, DataFlow::CfgNode {
override CallNode node;
FlaskAppAddUrlRule() { node.getFunction() = app_attr("add_url_rule").asCfgNode() }
override DataFlow::Node getUrlPatternArg() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("rule")]
}
override Function getARouteHandler() {
exists(DataFlow::Node view_func_arg, DataFlow::Node func_src |
view_func_arg.asCfgNode() in [node.getArg(2), node.getArgByName("view_func")] and
DataFlow::localFlow(func_src, view_func_arg) and
func_src.asExpr().(CallableExpr) = result.getDefinition()
)
}
}
// ---------------------------------------------------------------------------
// flask.Request taint modeling
// ---------------------------------------------------------------------------
// TODO: Do we even need this class? :|
/**
* A source of remote flow from a flask request.

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -1,98 +1,98 @@
| test.py:6 | fail | test_taint | name |
| test.py:6 | fail | test_taint | number |
| test.py:7 | ok | test_taint | foo |
| test.py:14 | ok | test_taint | request.environ |
| test.py:15 | ok | test_taint | request.environ.get(..) |
| test.py:17 | ok | test_taint | request.path |
| test.py:18 | ok | test_taint | request.full_path |
| test.py:19 | ok | test_taint | request.base_url |
| test.py:20 | ok | test_taint | request.url |
| test.py:23 | fail | test_taint | request.accept_charsets.best |
| test.py:24 | fail | test_taint | request.accept_charsets.best_match(..) |
| test.py:25 | ok | test_taint | request.accept_charsets[0] |
| test.py:26 | ok | test_taint | request.accept_encodings |
| test.py:27 | ok | test_taint | request.accept_languages |
| test.py:28 | ok | test_taint | request.accept_mimetypes |
| test.py:31 | ok | test_taint | request.access_control_request_headers |
| test.py:33 | ok | test_taint | request.access_control_request_method |
| test.py:35 | ok | test_taint | request.access_route |
| test.py:36 | ok | test_taint | request.access_route[0] |
| test.py:39 | ok | test_taint | request.args |
| test.py:40 | ok | test_taint | request.args['key'] |
| test.py:41 | ok | test_taint | request.args.getlist(..) |
| test.py:44 | ok | test_taint | request.authorization |
| test.py:45 | ok | test_taint | request.authorization['username'] |
| test.py:46 | fail | test_taint | request.authorization.username |
| test.py:49 | ok | test_taint | request.cache_control |
| test.py:51 | fail | test_taint | request.cache_control.max_age |
| test.py:52 | fail | test_taint | request.cache_control.max_stale |
| test.py:53 | fail | test_taint | request.cache_control.min_fresh |
| test.py:55 | ok | test_taint | request.content_encoding |
| test.py:57 | ok | test_taint | request.content_md5 |
| test.py:59 | ok | test_taint | request.content_type |
| test.py:62 | ok | test_taint | request.cookies |
| test.py:63 | ok | test_taint | request.cookies['key'] |
| test.py:65 | ok | test_taint | request.data |
| test.py:68 | ok | test_taint | request.files |
| test.py:69 | ok | test_taint | request.files['key'] |
| test.py:70 | fail | test_taint | request.files['key'].filename |
| test.py:71 | fail | test_taint | request.files['key'].stream |
| test.py:72 | ok | test_taint | request.files.getlist(..) |
| test.py:73 | fail | test_taint | request.files.getlist(..)[0].filename |
| test.py:74 | fail | test_taint | request.files.getlist(..)[0].stream |
| test.py:77 | ok | test_taint | request.form |
| test.py:78 | ok | test_taint | request.form['key'] |
| test.py:79 | ok | test_taint | request.form.getlist(..) |
| test.py:81 | ok | test_taint | request.get_data() |
| test.py:83 | ok | test_taint | request.get_json() |
| test.py:84 | ok | test_taint | request.get_json()['foo'] |
| test.py:85 | ok | test_taint | request.get_json()['foo']['bar'] |
| test.py:89 | ok | test_taint | request.headers |
| test.py:90 | ok | test_taint | request.headers['key'] |
| test.py:91 | fail | test_taint | request.headers.get_all(..) |
| test.py:92 | fail | test_taint | request.headers.getlist(..) |
| test.py:93 | ok | test_taint | list(..) |
| test.py:94 | fail | test_taint | request.headers.to_wsgi_list() |
| test.py:96 | ok | test_taint | request.json |
| test.py:97 | ok | test_taint | request.json['foo'] |
| test.py:98 | ok | test_taint | request.json['foo']['bar'] |
| test.py:100 | ok | test_taint | request.method |
| test.py:102 | ok | test_taint | request.mimetype |
| test.py:104 | ok | test_taint | request.mimetype_params |
| test.py:106 | ok | test_taint | request.origin |
| test.py:109 | ok | test_taint | request.pragma |
| test.py:111 | ok | test_taint | request.query_string |
| test.py:113 | ok | test_taint | request.referrer |
| test.py:115 | ok | test_taint | request.remote_addr |
| test.py:117 | ok | test_taint | request.remote_user |
| test.py:120 | ok | test_taint | request.stream |
| test.py:121 | ok | test_taint | request.input_stream |
| test.py:123 | ok | test_taint | request.url |
| test.py:125 | ok | test_taint | request.user_agent |
| test.py:128 | ok | test_taint | request.values |
| test.py:129 | ok | test_taint | request.values['key'] |
| test.py:130 | ok | test_taint | request.values.getlist(..) |
| test.py:133 | ok | test_taint | request.view_args |
| test.py:134 | ok | test_taint | request.view_args['key'] |
| test.py:138 | ok | test_taint | request.script_root |
| test.py:139 | ok | test_taint | request.url_root |
| test.py:143 | ok | test_taint | request.charset |
| test.py:144 | ok | test_taint | request.url_charset |
| test.py:148 | ok | test_taint | request.date |
| test.py:151 | ok | test_taint | request.endpoint |
| test.py:156 | ok | test_taint | request.host |
| test.py:157 | ok | test_taint | request.host_url |
| test.py:159 | ok | test_taint | request.scheme |
| test.py:161 | ok | test_taint | request.script_root |
| test.py:169 | ok | test_taint | request.args |
| test.py:170 | ok | test_taint | a |
| test.py:171 | ok | test_taint | b |
| test.py:173 | ok | test_taint | request.args['key'] |
| test.py:174 | ok | test_taint | a['key'] |
| test.py:175 | ok | test_taint | b['key'] |
| test.py:177 | ok | test_taint | request.args.getlist(..) |
| test.py:178 | ok | test_taint | a.getlist(..) |
| test.py:179 | ok | test_taint | b.getlist(..) |
| test.py:180 | ok | test_taint | gl(..) |
| test.py:187 | ok | test_taint | req.path |
| test.py:188 | ok | test_taint | gd() |
| taint_test.py:6 | ok | test_taint | name |
| taint_test.py:6 | ok | test_taint | number |
| taint_test.py:7 | ok | test_taint | foo |
| taint_test.py:14 | ok | test_taint | request.environ |
| taint_test.py:15 | ok | test_taint | request.environ.get(..) |
| taint_test.py:17 | ok | test_taint | request.path |
| taint_test.py:18 | ok | test_taint | request.full_path |
| taint_test.py:19 | ok | test_taint | request.base_url |
| taint_test.py:20 | ok | test_taint | request.url |
| taint_test.py:23 | fail | test_taint | request.accept_charsets.best |
| taint_test.py:24 | fail | test_taint | request.accept_charsets.best_match(..) |
| taint_test.py:25 | ok | test_taint | request.accept_charsets[0] |
| taint_test.py:26 | ok | test_taint | request.accept_encodings |
| taint_test.py:27 | ok | test_taint | request.accept_languages |
| taint_test.py:28 | ok | test_taint | request.accept_mimetypes |
| taint_test.py:31 | ok | test_taint | request.access_control_request_headers |
| taint_test.py:33 | ok | test_taint | request.access_control_request_method |
| taint_test.py:35 | ok | test_taint | request.access_route |
| taint_test.py:36 | ok | test_taint | request.access_route[0] |
| taint_test.py:39 | ok | test_taint | request.args |
| taint_test.py:40 | ok | test_taint | request.args['key'] |
| taint_test.py:41 | ok | test_taint | request.args.getlist(..) |
| taint_test.py:44 | ok | test_taint | request.authorization |
| taint_test.py:45 | ok | test_taint | request.authorization['username'] |
| taint_test.py:46 | fail | test_taint | request.authorization.username |
| taint_test.py:49 | ok | test_taint | request.cache_control |
| taint_test.py:51 | fail | test_taint | request.cache_control.max_age |
| taint_test.py:52 | fail | test_taint | request.cache_control.max_stale |
| taint_test.py:53 | fail | test_taint | request.cache_control.min_fresh |
| taint_test.py:55 | ok | test_taint | request.content_encoding |
| taint_test.py:57 | ok | test_taint | request.content_md5 |
| taint_test.py:59 | ok | test_taint | request.content_type |
| taint_test.py:62 | ok | test_taint | request.cookies |
| taint_test.py:63 | ok | test_taint | request.cookies['key'] |
| taint_test.py:65 | ok | test_taint | request.data |
| taint_test.py:68 | ok | test_taint | request.files |
| taint_test.py:69 | ok | test_taint | request.files['key'] |
| taint_test.py:70 | fail | test_taint | request.files['key'].filename |
| taint_test.py:71 | fail | test_taint | request.files['key'].stream |
| taint_test.py:72 | ok | test_taint | request.files.getlist(..) |
| taint_test.py:73 | fail | test_taint | request.files.getlist(..)[0].filename |
| taint_test.py:74 | fail | test_taint | request.files.getlist(..)[0].stream |
| taint_test.py:77 | ok | test_taint | request.form |
| taint_test.py:78 | ok | test_taint | request.form['key'] |
| taint_test.py:79 | ok | test_taint | request.form.getlist(..) |
| taint_test.py:81 | ok | test_taint | request.get_data() |
| taint_test.py:83 | ok | test_taint | request.get_json() |
| taint_test.py:84 | ok | test_taint | request.get_json()['foo'] |
| taint_test.py:85 | ok | test_taint | request.get_json()['foo']['bar'] |
| taint_test.py:89 | ok | test_taint | request.headers |
| taint_test.py:90 | ok | test_taint | request.headers['key'] |
| taint_test.py:91 | fail | test_taint | request.headers.get_all(..) |
| taint_test.py:92 | fail | test_taint | request.headers.getlist(..) |
| taint_test.py:93 | ok | test_taint | list(..) |
| taint_test.py:94 | fail | test_taint | request.headers.to_wsgi_list() |
| taint_test.py:96 | ok | test_taint | request.json |
| taint_test.py:97 | ok | test_taint | request.json['foo'] |
| taint_test.py:98 | ok | test_taint | request.json['foo']['bar'] |
| taint_test.py:100 | ok | test_taint | request.method |
| taint_test.py:102 | ok | test_taint | request.mimetype |
| taint_test.py:104 | ok | test_taint | request.mimetype_params |
| taint_test.py:106 | ok | test_taint | request.origin |
| taint_test.py:109 | ok | test_taint | request.pragma |
| taint_test.py:111 | ok | test_taint | request.query_string |
| taint_test.py:113 | ok | test_taint | request.referrer |
| taint_test.py:115 | ok | test_taint | request.remote_addr |
| taint_test.py:117 | ok | test_taint | request.remote_user |
| taint_test.py:120 | ok | test_taint | request.stream |
| taint_test.py:121 | ok | test_taint | request.input_stream |
| taint_test.py:123 | ok | test_taint | request.url |
| taint_test.py:125 | ok | test_taint | request.user_agent |
| taint_test.py:128 | ok | test_taint | request.values |
| taint_test.py:129 | ok | test_taint | request.values['key'] |
| taint_test.py:130 | ok | test_taint | request.values.getlist(..) |
| taint_test.py:133 | ok | test_taint | request.view_args |
| taint_test.py:134 | ok | test_taint | request.view_args['key'] |
| taint_test.py:138 | ok | test_taint | request.script_root |
| taint_test.py:139 | ok | test_taint | request.url_root |
| taint_test.py:143 | ok | test_taint | request.charset |
| taint_test.py:144 | ok | test_taint | request.url_charset |
| taint_test.py:148 | ok | test_taint | request.date |
| taint_test.py:151 | ok | test_taint | request.endpoint |
| taint_test.py:156 | ok | test_taint | request.host |
| taint_test.py:157 | ok | test_taint | request.host_url |
| taint_test.py:159 | ok | test_taint | request.scheme |
| taint_test.py:161 | ok | test_taint | request.script_root |
| taint_test.py:169 | ok | test_taint | request.args |
| taint_test.py:170 | ok | test_taint | a |
| taint_test.py:171 | ok | test_taint | b |
| taint_test.py:173 | ok | test_taint | request.args['key'] |
| taint_test.py:174 | ok | test_taint | a['key'] |
| taint_test.py:175 | ok | test_taint | b['key'] |
| taint_test.py:177 | ok | test_taint | request.args.getlist(..) |
| taint_test.py:178 | ok | test_taint | a.getlist(..) |
| taint_test.py:179 | ok | test_taint | b.getlist(..) |
| taint_test.py:180 | ok | test_taint | gl(..) |
| taint_test.py:187 | ok | test_taint | req.path |
| taint_test.py:188 | ok | test_taint | gd() |

View File

@@ -0,0 +1,67 @@
import flask
from flask import Flask, request, make_response
app = Flask(__name__)
@app.route("/") # $routeSetup="/"
def hello_world(): # $routeHandler
return "Hello World!"
from flask.views import MethodView
class MyView(MethodView):
def get(self, user_id): # $f-:routeHandler
if user_id is None:
# return a list of users
pass
else:
# expose a single user
pass
the_view = MyView.as_view('my_view')
app.add_url_rule('/the/', defaults={'user_id': None}, # $routeSetup="/the/"
view_func=the_view, methods=['GET',])
@app.route("/dangerous") # $routeSetup="/dangerous"
def dangerous(): # $routeHandler
return request.args.get('payload')
@app.route("/dangerous-with-cfg-split") # $routeSetup="/dangerous-with-cfg-split"
def dangerous2(): # $routeHandler
x = request.form['param0']
if request.method == "POST":
return request.form['param1']
return None
@app.route("/unsafe") # $routeSetup="/unsafe"
def unsafe(): # $routeHandler
first_name = request.args.get('name', '')
return make_response("Your name is " + first_name)
@app.route("/safe") # $routeSetup="/safe"
def safe(): # $routeHandler
first_name = request.args.get('name', '')
return make_response("Your name is " + escape(first_name))
@app.route("/hello/<name>") # $routeSetup="/hello/<name>"
def hello(name): # $routeHandler $routedParameter=name
return make_response("Your name is " + name)
@app.route("/foo/<path:subpath>") # $routeSetup="/foo/<path:subpath>"
def foo(subpath): # $routeHandler $routedParameter=subpath
return make_response("The subpath is " + subpath)
@app.route("/multiple/") # $routeSetup="/multiple/"
@app.route("/multiple/foo/<foo>") # $routeSetup="/multiple/foo/<foo>"
@app.route("/multiple/bar/<bar>") # $routeSetup="/multiple/bar/<bar>"
def multiple(foo=None, bar=None): # $routeHandler $routedParameter=foo $routedParameter=bar
return make_response("foo={!r} bar={!r}".format(foo, bar))
@app.route("/complex/<string(length=2):lang_code>") # $routeSetup="/complex/<string(length=2):lang_code>"
def complex(lang_code): # $routeHandler $routedParameter=lang_code
return make_response("lang_code {}".format(lang_code))
if __name__ == "__main__":
app.run(debug=True)

View File

@@ -0,0 +1,31 @@
import flask
from flask import Flask, make_response
app = Flask(__name__)
SOME_ROUTE = "/some/route"
@app.route(SOME_ROUTE) # $routeSetup="/some/route"
def some_route(): # $routeHandler
return make_response("some_route")
def index(): # $routeHandler
return make_response("index")
app.add_url_rule('/index', 'index', index) # $routeSetup="/index"
# We don't support this yet, and I think that's OK
def later_set(): # $f-:routeHandler
return make_response("later_set")
app.add_url_rule('/later-set', 'later_set', view_func=None) # $routeSetup="/later-set"
app.view_functions['later_set'] = later_set
@app.route(UNKNOWN_ROUTE) # $routeSetup
def unkown_route(foo, bar): # $routeHandler $routedParameter=foo $routedParameter=bar
return make_response("unkown_route")
if __name__ == "__main__":
app.run(debug=True)

View File

@@ -1,8 +1,8 @@
from flask import Flask, request
app = Flask(__name__)
@app.route('/test_taint/<name>/<int:number>')
def test_taint(name = "World!", number="0", foo="foo"):
@app.route("/test_taint/<name>/<int:number>") # $routeSetup="/test_taint/<name>/<int:number>"
def test_taint(name = "World!", number="0", foo="foo"): # $routeHandler $routedParameter=name $routedParameter=number
ensure_tainted(name, number)
ensure_not_tainted(foo)
@@ -191,8 +191,8 @@ def test_taint(name = "World!", number="0", foo="foo"):
@app.route('/debug/<foo>/<bar>', methods=['GET'])
def debug(foo, bar):
@app.route("/debug/<foo>/<bar>", methods=['GET']) # $routeSetup="/debug/<foo>/<bar>"
def debug(foo, bar): # $routeHandler $routedParameter=foo $routedParameter=bar
print("request.view_args", request.view_args)
print("request.headers {!r}".format(request.headers))
@@ -202,8 +202,8 @@ def debug(foo, bar):
return 'ok'
@app.route('/stream', methods=['POST'])
def stream():
@app.route("/stream", methods=['POST']) # $routeSetup="/stream"
def stream(): # $routeHandler
print(request.path)
s = request.stream
print(s)
@@ -212,8 +212,8 @@ def stream():
return 'ok'
@app.route('/input_stream', methods=['POST'])
def input_stream():
@app.route("/input_stream", methods=['POST']) # $routeSetup="/input_stream"
def input_stream(): # $routeHandler
print(request.path)
s = request.input_stream
print(s)
@@ -223,15 +223,15 @@ def input_stream():
return 'ok'
@app.route('/form', methods=['POST'])
def form():
@app.route("/form", methods=['POST']) # $routeSetup="/form"
def form(): # $routeHandler
print(request.path)
print("request.form", request.form)
return 'ok'
@app.route('/cache_control', methods=['POST'])
def cache_control():
@app.route("/cache_control", methods=['POST']) # $routeSetup="/cache_control"
def cache_control(): # $routeHandler
print(request.path)
print("request.cache_control.max_age", request.cache_control.max_age, type(request.cache_control.max_age))
print("request.cache_control.max_stale", request.cache_control.max_stale, type(request.cache_control.max_stale))
@@ -239,16 +239,16 @@ def cache_control():
return 'ok'
@app.route('/file_upload', methods=['POST'])
def file_upload():
@app.route("/file_upload", methods=['POST']) # $routeSetup="/file_upload"
def file_upload(): # $routeHandler
print(request.path)
for k,v in request.files.items():
print(k, v, v.name, v.filename, v.stream)
return 'ok'
@app.route('/args', methods=['GET'])
def args():
@app.route("/args", methods=['GET']) # $routeSetup="/args"
def args(): # $routeHandler
print(request.path)
print("request.args", request.args)

View File

@@ -22,8 +22,8 @@ class SystemCommandExecutionTest extends InlineExpectationsTest {
override string getARelevantTag() { result = "getCommand" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(SystemCommandExecution sce, DataFlow::Node command |
exists(location.getFile().getRelativePath()) and
command = sce.getCommand() and
location = command.getLocation() and
element = command.toString() and
@@ -32,3 +32,39 @@ class SystemCommandExecutionTest extends InlineExpectationsTest {
)
}
}
class HttpServerRouteSetupTest extends InlineExpectationsTest {
HttpServerRouteSetupTest() { this = "HttpServerRouteSetupTest" }
override string getARelevantTag() { result in ["routeSetup", "routeHandler", "routedParameter"] }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(HTTP::Server::RouteSetup setup |
location = setup.getLocation() and
element = setup.toString() and
(
value = "\"" + setup.getUrlPattern() + "\""
or
not exists(setup.getUrlPattern()) and
value = ""
) and
tag = "routeSetup"
)
or
exists(HTTP::Server::RouteSetup setup, Function func |
func = setup.getARouteHandler() and
location = func.getLocation() and
element = func.toString() and
value = "" and
tag = "routeHandler"
)
or
exists(HTTP::Server::RouteSetup setup, Parameter param |
param = setup.getARoutedParameter() and
location = param.getLocation() and
element = param.toString() and
value = param.asName().getId() and
tag = "routedParameter"
)
}
}