Merge pull request #4541 from RasmusWL/python-port-reflected-xss

Python: Port reflected XSS query
This commit is contained in:
Taus
2020-10-30 19:17:33 +01:00
committed by GitHub
19 changed files with 1582 additions and 79 deletions

View File

@@ -0,0 +1,38 @@
/**
* @name Reflected server-side cross-site scripting
* @description Writing user input directly to a web page
* allows for a cross-site scripting vulnerability.
* @kind path-problem
* @problem.severity error
* @sub-severity high
* @precision high
* @id py/reflective-xss
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import python
import experimental.dataflow.DataFlow
import experimental.dataflow.TaintTracking
import experimental.semmle.python.Concepts
import experimental.dataflow.RemoteFlowSources
import DataFlow::PathGraph
class ReflectedXssConfiguration extends TaintTracking::Configuration {
ReflectedXssConfiguration() { this = "ReflectedXssConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(HTTP::Server::HttpResponse response |
response.getMimetype().toLowerCase() = "text/html" and
sink = response.getBody()
)
}
}
from ReflectedXssConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
source.getNode(), "a user-provided value"

View File

@@ -228,7 +228,7 @@ module HTTP {
/** Provides classes for modeling HTTP servers. */
module Server {
/**
* An data-flow node that sets up a route on a server.
* A 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.
@@ -254,7 +254,7 @@ module HTTP {
/** Provides a class for modeling new HTTP routing APIs. */
module RouteSetup {
/**
* An data-flow node that sets up a route on a server.
* A 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.
@@ -287,5 +287,60 @@ module HTTP {
override string getSourceType() { result = "RoutedParameter" }
}
/**
* A data-flow node that creates a HTTP response on a server.
*
* Note: we don't require that this response must be sent to a client (a kind of
* "if a tree falls in a forest and nobody hears it" situation).
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HttpResponse::Range` instead.
*/
class HttpResponse extends DataFlow::Node {
HttpResponse::Range range;
HttpResponse() { this = range }
/** Gets the data-flow node that specifies the body of this HTTP response. */
DataFlow::Node getBody() { result = range.getBody() }
/** Gets the mimetype of this HTTP response, if it can be statically determined. */
string getMimetype() { result = range.getMimetype() }
}
/** Provides a class for modeling new HTTP response APIs. */
module HttpResponse {
/**
* A data-flow node that creates a HTTP response on a server.
*
* Note: we don't require that this response must be sent to a client (a kind of
* "if a tree falls in a forest and nobody hears it" situation).
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HttpResponse` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the data-flow node that specifies the body of this HTTP response. */
abstract DataFlow::Node getBody();
/** Gets the data-flow node that specifies the content-type/mimetype of this HTTP response, if any. */
abstract DataFlow::Node getMimetypeOrContentTypeArg();
/** Gets the default mimetype that should be used if `getMimetypeOrContentTypeArg` has no results. */
abstract string getMimetypeDefault();
/** Gets the mimetype of this HTTP response, if it can be statically determined. */
string getMimetype() {
exists(StrConst str |
DataFlow::localFlow(DataFlow::exprNode(str), this.getMimetypeOrContentTypeArg()) and
result = str.getText().splitAt(";", 0)
)
or
not exists(this.getMimetypeOrContentTypeArg()) and
result = this.getMimetypeDefault()
}
}
}
}
}

View File

@@ -499,7 +499,18 @@ private module Django {
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node http_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["request", "HttpRequest"] and
attr_name in ["request",
// request
"HttpRequest",
// response
"response", "HttpResponse",
// HttpResponse subclasses
"HttpResponseRedirect", "HttpResponsePermanentRedirect", "HttpResponseNotModified",
"HttpResponseBadRequest", "HttpResponseNotFound", "HttpResponseForbidden",
"HttpResponseNotAllowed", "HttpResponseGone", "HttpResponseServerError",
"JsonResponse",
// HttpResponse-like classes
"StreamingHttpResponse", "FileResponse"] and
(
t.start() and
result = DataFlow::importNode("django.http" + "." + attr_name)
@@ -627,6 +638,941 @@ private module Django {
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
}
// -------------------------------------------------------------------------
// django.http.response
// -------------------------------------------------------------------------
/** Gets a reference to the `django.http.response` module. */
DataFlow::Node response() { result = http_attr("response") }
/** Provides models for the `django.http.response` module */
module response {
/**
* Gets a reference to the attribute `attr_name` of the `django.http.response` module.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node response_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["HttpResponse",
// HttpResponse subclasses
"HttpResponseRedirect", "HttpResponsePermanentRedirect", "HttpResponseNotModified",
"HttpResponseBadRequest", "HttpResponseNotFound", "HttpResponseForbidden",
"HttpResponseNotAllowed", "HttpResponseGone", "HttpResponseServerError",
"JsonResponse",
// HttpResponse-like classes
"StreamingHttpResponse", "FileResponse"] and
(
t.start() and
result = DataFlow::importNode("django.http.response" + "." + attr_name)
or
t.startInAttr(attr_name) and
result = response()
)
or
// Due to bad performance when using normal setup with `response_attr(t2, attr_name).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
response_attr_first_join(t2, attr_name, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate response_attr_first_join(
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(response_attr(t2, attr_name), res, summary)
}
/**
* Gets a reference to the attribute `attr_name` of the `django.http.response` module.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node response_attr(string attr_name) {
result = response_attr(DataFlow::TypeTracker::end(), attr_name)
}
/**
* Provides models for the `django.http.response.HttpResponse` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponse.
*/
module HttpResponse {
/** Gets a reference to the `django.http.response.HttpResponse` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponse")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponse` alias
t.start() and
result = http_attr("HttpResponse")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponse` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponse`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponse::instance()` predicate to get references to instances of `django.http.response.HttpResponse`.
*/
abstract class InstanceSource extends HTTP::Server::HttpResponse::Range, DataFlow::Node {
}
/** A direct instantiation of `django.http.response.HttpResponse`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() {
result.asCfgNode() in [node.getArg(1), node.getArgByName("content_type")]
}
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponse`. */
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 `django.http.response.HttpResponse`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
// ---------------------------------------------------------------------------
// HttpResponse subclasses
// see https://docs.djangoproject.com/en/3.1/ref/request-response/#httpresponse-subclasses
// ---------------------------------------------------------------------------
/**
* Provides models for the `django.http.response.HttpResponseRedirect` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponseRedirect.
*/
module HttpResponseRedirect {
/** Gets a reference to the `django.http.response.HttpResponseRedirect` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponseRedirect")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponseRedirect` alias
t.start() and
result = http_attr("HttpResponseRedirect")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponseRedirect` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponseRedirect`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponseRedirect::instance()` predicate to get references to instances of `django.http.response.HttpResponseRedirect`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseRedirect`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("redirect_to")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponseRedirect`. */
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 `django.http.response.HttpResponseRedirect`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.HttpResponsePermanentRedirect` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponsePermanentRedirect.
*/
module HttpResponsePermanentRedirect {
/** Gets a reference to the `django.http.response.HttpResponsePermanentRedirect` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponsePermanentRedirect")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponsePermanentRedirect` alias
t.start() and
result = http_attr("HttpResponsePermanentRedirect")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponsePermanentRedirect` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponsePermanentRedirect`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponsePermanentRedirect::instance()` predicate to get references to instances of `django.http.response.HttpResponsePermanentRedirect`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponsePermanentRedirect`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("redirect_to")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponsePermanentRedirect`. */
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 `django.http.response.HttpResponsePermanentRedirect`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.HttpResponseNotModified` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponseNotModified.
*/
module HttpResponseNotModified {
/** Gets a reference to the `django.http.response.HttpResponseNotModified` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponseNotModified")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponseNotModified` alias
t.start() and
result = http_attr("HttpResponseNotModified")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponseNotModified` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponseNotModified`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponseNotModified::instance()` predicate to get references to instances of `django.http.response.HttpResponseNotModified`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseNotModified`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() { none() }
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { none() }
}
/** Gets a reference to an instance of `django.http.response.HttpResponseNotModified`. */
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 `django.http.response.HttpResponseNotModified`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.HttpResponseBadRequest` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponseBadRequest.
*/
module HttpResponseBadRequest {
/** Gets a reference to the `django.http.response.HttpResponseBadRequest` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponseBadRequest")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponseBadRequest` alias
t.start() and
result = http_attr("HttpResponseBadRequest")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponseBadRequest` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponseBadRequest`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponseBadRequest::instance()` predicate to get references to instances of `django.http.response.HttpResponseBadRequest`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseBadRequest`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponseBadRequest`. */
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 `django.http.response.HttpResponseBadRequest`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.HttpResponseNotFound` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponseNotFound.
*/
module HttpResponseNotFound {
/** Gets a reference to the `django.http.response.HttpResponseNotFound` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponseNotFound")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponseNotFound` alias
t.start() and
result = http_attr("HttpResponseNotFound")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponseNotFound` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponseNotFound`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponseNotFound::instance()` predicate to get references to instances of `django.http.response.HttpResponseNotFound`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseNotFound`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponseNotFound`. */
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 `django.http.response.HttpResponseNotFound`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.HttpResponseForbidden` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponseForbidden.
*/
module HttpResponseForbidden {
/** Gets a reference to the `django.http.response.HttpResponseForbidden` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponseForbidden")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponseForbidden` alias
t.start() and
result = http_attr("HttpResponseForbidden")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponseForbidden` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponseForbidden`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponseForbidden::instance()` predicate to get references to instances of `django.http.response.HttpResponseForbidden`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseForbidden`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponseForbidden`. */
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 `django.http.response.HttpResponseForbidden`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.HttpResponseNotAllowed` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponseNotAllowed.
*/
module HttpResponseNotAllowed {
/** Gets a reference to the `django.http.response.HttpResponseNotAllowed` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponseNotAllowed")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponseNotAllowed` alias
t.start() and
result = http_attr("HttpResponseNotAllowed")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponseNotAllowed` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponseNotAllowed`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponseNotAllowed::instance()` predicate to get references to instances of `django.http.response.HttpResponseNotAllowed`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseNotAllowed`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
// First argument is permitted methods
result.asCfgNode() in [node.getArg(1), node.getArgByName("content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponseNotAllowed`. */
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 `django.http.response.HttpResponseNotAllowed`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.HttpResponseGone` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponseGone.
*/
module HttpResponseGone {
/** Gets a reference to the `django.http.response.HttpResponseGone` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponseGone")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponseGone` alias
t.start() and
result = http_attr("HttpResponseGone")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponseGone` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponseGone`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponseGone::instance()` predicate to get references to instances of `django.http.response.HttpResponseGone`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseGone`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponseGone`. */
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 `django.http.response.HttpResponseGone`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.HttpResponseServerError` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponseServerError.
*/
module HttpResponseServerError {
/** Gets a reference to the `django.http.response.HttpResponseServerError` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("HttpResponseServerError")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.HttpResponseServerError` alias
t.start() and
result = http_attr("HttpResponseServerError")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponseServerError` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.HttpResponseServerError`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `HttpResponseServerError::instance()` predicate to get references to instances of `django.http.response.HttpResponseServerError`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseServerError`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.HttpResponseServerError`. */
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 `django.http.response.HttpResponseServerError`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.JsonResponse` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#jsonresponse-objects.
*/
module JsonResponse {
/** Gets a reference to the `django.http.response.JsonResponse` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("JsonResponse")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.JsonResponse` alias
t.start() and
result = http_attr("JsonResponse")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.JsonResponse` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.JsonResponse`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `JsonResponse::instance()` predicate to get references to instances of `django.http.response.JsonResponse`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.JsonResponse`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("data")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "application/json" }
}
/** Gets a reference to an instance of `django.http.response.JsonResponse`. */
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 `django.http.response.JsonResponse`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
// ---------------------------------------------------------------------------
// HttpResponse-like classes
// ---------------------------------------------------------------------------
/**
* Provides models for the `django.http.response.StreamingHttpResponse` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#streaminghttpresponse-objects.
*/
module StreamingHttpResponse {
/** Gets a reference to the `django.http.response.StreamingHttpResponse` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("StreamingHttpResponse")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.StreamingHttpResponse` alias
t.start() and
result = http_attr("StreamingHttpResponse")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.StreamingHttpResponse` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.StreamingHttpResponse`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `StreamingHttpResponse::instance()` predicate to get references to instances of `django.http.response.StreamingHttpResponse`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.StreamingHttpResponse`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("streaming_content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.StreamingHttpResponse`. */
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 `django.http.response.StreamingHttpResponse`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/**
* Provides models for the `django.http.response.FileResponse` class
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#fileresponse-objects.
*/
module FileResponse {
/** Gets a reference to the `django.http.response.FileResponse` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = response_attr("FileResponse")
or
// TODO: remove/expand this part of the template as needed
// Handle `http.FileResponse` alias
t.start() and
result = http_attr("FileResponse")
or
// subclass
result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.FileResponse` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `django.http.response.FileResponse`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `FileResponse::instance()` predicate to get references to instances of `django.http.response.FileResponse`.
*/
abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
/** A direct instantiation of `django.http.response.FileResponse`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("streaming_content")]
}
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html; charset=utf-8" }
}
/** Gets a reference to an instance of `django.http.response.FileResponse`. */
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 `django.http.response.FileResponse`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
/** Gets a reference to the `django.http.response.HttpResponse.write` function. */
private DataFlow::Node write(
django::http::response::HttpResponse::InstanceSource instance, DataFlow::TypeTracker t
) {
t.startInAttr("write") and
instance = django::http::response::HttpResponse::instance() and
result = instance
or
exists(DataFlow::TypeTracker t2 | result = write(instance, t2).track(t2, t))
}
/** Gets a reference to the `django.http.response.HttpResponse.write` function. */
DataFlow::Node write(django::http::response::HttpResponse::InstanceSource instance) {
result = write(instance, DataFlow::TypeTracker::end())
}
/**
* A call to the `django.http.response.HttpResponse.write` function.
*
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponse.write
*/
class HttpResponseWriteCall extends HTTP::Server::HttpResponse::Range, DataFlow::CfgNode {
override CallNode node;
HTTP::Server::HttpResponse::Range instance;
HttpResponseWriteCall() { node.getFunction() = write(instance).asCfgNode() }
override DataFlow::Node getBody() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("content")]
}
override DataFlow::Node getMimetypeOrContentTypeArg() {
result = instance.getMimetypeOrContentTypeArg()
}
override string getMimetypeDefault() { result = instance.getMimetypeDefault() }
}
}
}
}

View File

@@ -15,6 +15,9 @@ private import experimental.semmle.python.frameworks.Werkzeug
* 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
@@ -26,21 +29,52 @@ private module FlaskModel {
/** 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"] 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. */
private DataFlow::Node request(DataFlow::TypeTracker t) {
t.start() and
result = DataFlow::importNode("flask.request")
or
t.startInAttr("request") and
result = flask()
or
exists(DataFlow::TypeTracker t2 | result = request(t2).track(t2, t))
}
DataFlow::Node request() { result = flask_attr("request") }
/** Gets a reference to the `flask.request` object. */
DataFlow::Node request() { result = request(DataFlow::TypeTracker::end()) }
/** Gets a reference to the `flask.make_response` function. */
DataFlow::Node make_response() { result = flask_attr("make_response") }
/**
* Provides models for the `flask.Flask` class
@@ -96,7 +130,7 @@ private module FlaskModel {
* 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"] and
attr_name in ["route", "add_url_rule", "make_response"] and
t.startInAttr(attr_name) and
result = flask::Flask::instance()
or
@@ -131,9 +165,99 @@ private module FlaskModel {
/** 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()) }
}
}
/**
* 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()]
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `flask.Response` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of an instance of `flask.Response`.
*
* This can include instantiation of the class, return value from function
* calls, or a special parameter that will be set when functions are call by external
* library.
*
* Use `Response::instance()` predicate to get references to instances of `flask.Response`.
*/
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;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
override DataFlow::Node getBody() { result.asCfgNode() = node.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")]
}
/** 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")]
}
override DataFlow::Node getMimetypeOrContentTypeArg() {
result = this.getContentTypeArg()
or
// content_type argument takes priority over mimetype argument
not exists(this.getContentTypeArg()) and
result = this.getMimetypeArg()
}
}
/** Gets a reference to an instance of `flask.Response`. */
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.Response`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
}
// ---------------------------------------------------------------------------
// routing modeling
// ---------------------------------------------------------------------------
@@ -324,8 +448,50 @@ private module FlaskModel {
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
// ---------------------------------------------------------------------------
/**
* 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 |
routeHandler = any(FlaskRouteSetup rs).getARouteHandler() and
node = routeHandler.getAReturnValueFlowNode()
)
}
override DataFlow::Node getBody() { result = this }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "text/html" }
}
}

View File

@@ -1,2 +1,12 @@
import python
import experimental.meta.ConceptsTest
class DedicatedResponseTest extends HttpServerHttpResponseTest {
DedicatedResponseTest() { file.getShortName() = "response_test.py" }
}
class OtherResponseTest extends HttpServerHttpResponseTest {
OtherResponseTest() { not this instanceof DedicatedResponseTest }
override string getARelevantTag() { result = "HttpResponse" }
}

View File

@@ -2,28 +2,52 @@ from django.http.response import HttpResponse, HttpResponseRedirect, JsonRespons
# Not an XSS sink, since the Content-Type is not "text/html"
# FP reported in https://github.com/github/codeql-python-team/issues/38
def fp_json_response(request):
def safe__json_response(request):
# implicitly sets Content-Type to "application/json"
return JsonResponse({"foo": request.GET.get("foo")})
return JsonResponse({"foo": request.GET.get("foo")}) # $HttpResponse $mimetype=application/json $responseBody=Dict
# Not an XSS sink, since the Content-Type is not "text/html"
def fp_manual_json_response(request):
def safe__manual_json_response(request):
json_data = '{"json": "{}"}'.format(request.GET.get("foo"))
return HttpResponse(json_data, content_type="application/json")
return HttpResponse(json_data, content_type="application/json") # $HttpResponse $mimetype=application/json $responseBody=json_data
# Not an XSS sink, since the Content-Type is not "text/html"
def fp_manual_content_type(request):
return HttpResponse('<img src="0" onerror="alert(1)">', content_type="text/plain")
def safe__manual_content_type(request):
return HttpResponse('<img src="0" onerror="alert(1)">', content_type="text/plain") # $HttpResponse $mimetype=text/plain $responseBody='<img src="0" onerror="alert(1)">'
# XSS FP reported in https://github.com/github/codeql/issues/3466
# Note: This should be a open-redirect sink, but not a XSS sink.
def fp_redirect(request):
return HttpResponseRedirect(request.GET.get("next"))
# Note: This should be an open-redirect sink, but not an XSS sink.
def or__redirect(request):
return HttpResponseRedirect(request.GET.get("next")) # $HttpResponse $mimetype=text/html; charset=utf-8 $responseBody=Attribute()
# Ensure that simple subclasses are still vuln to XSS
def tp_not_found(request):
return HttpResponseNotFound(request.GET.get("name"))
def xss__not_found(request):
return HttpResponseNotFound(request.GET.get("name")) # $HttpResponse $mimetype=text/html; charset=utf-8 $responseBody=Attribute()
# Ensure we still have a XSS sink when manually setting the content_type to HTML
def tp_manual_response_type(request):
return HttpResponse(request.GET.get("name"), content_type="text/html; charset=utf-8")
# Ensure we still have an XSS sink when manually setting the content_type to HTML
def xss__manual_response_type(request):
return HttpResponse(request.GET.get("name"), content_type="text/html; charset=utf-8") # $HttpResponse $mimetype=text/html $responseBody=Attribute()
def xss__write(request):
response = HttpResponse() # $HttpResponse $mimetype=text/html; charset=utf-8
response.write(request.GET.get("name")) # $HttpResponse $mimetype=text/html; charset=utf-8 $responseBody=Attribute()
# This is safe but probably a bug if the argument to `write` is not a result of `json.dumps` or similar.
def safe__write_json(request):
response = JsonResponse() # $HttpResponse $mimetype=application/json
response.write(request.GET.get("name")) # $HttpResponse $mimetype=application/json $responseBody=Attribute()
# Ensure manual subclasses are vulnerable
class CustomResponse(HttpResponse):
def __init__(self, banner, content, *args, **kwargs):
super().__init__(content, *args, content_type="text/html", **kwargs)
def xss__custom_response(request):
return CustomResponse("ACME Responses", request.GET("name")) # $HttpResponse $f-:mimetype=text/html $f-:responseBody=Attribute() $f+:responseBody="ACME Responses"
class CustomJsonResponse(JsonResponse):
def __init__(self, banner, content, *args, **kwargs):
super().__init__(content, *args, content_type="text/html", **kwargs)
def safe__custom_json_response(request):
return CustomJsonResponse("ACME Responses", {"foo": request.GET.get("foo")}) # $HttpResponse $mimetype=application/json $f-:responseBody=Dict $f+:responseBody="ACME Responses"

View File

@@ -5,20 +5,20 @@ from django.views.generic import View
def url_match_xss(request, foo, bar, no_taint=None): # $routeHandler $routedParameter=foo $routedParameter=bar
return HttpResponse('url_match_xss: {} {}'.format(foo, bar))
return HttpResponse('url_match_xss: {} {}'.format(foo, bar)) # $HttpResponse
def get_params_xss(request): # $routeHandler
return HttpResponse(request.GET.get("untrusted"))
return HttpResponse(request.GET.get("untrusted")) # $HttpResponse
def post_params_xss(request): # $routeHandler
return HttpResponse(request.POST.get("untrusted"))
return HttpResponse(request.POST.get("untrusted")) # $HttpResponse
def http_resp_write(request): # $routeHandler
rsp = HttpResponse()
rsp.write(request.GET.get("untrusted"))
rsp = HttpResponse() # $HttpResponse
rsp.write(request.GET.get("untrusted")) # $HttpResponse
return rsp
@@ -27,22 +27,22 @@ class Foo(object):
def post(self, request, untrusted): # $f-:routeHandler $f-:routedParameter=untrusted
return HttpResponse('Foo post: {}'.format(untrusted))
return HttpResponse('Foo post: {}'.format(untrusted)) # $HttpResponse
class ClassView(View, Foo):
def get(self, request, untrusted): # $f-:routeHandler $f-:routedParameter=untrusted
return HttpResponse('ClassView get: {}'.format(untrusted))
return HttpResponse('ClassView get: {}'.format(untrusted)) # $HttpResponse
def show_articles(request, page_number=1): # $routeHandler $routedParameter=page_number
page_number = int(page_number)
return HttpResponse('articles page: {}'.format(page_number))
return HttpResponse('articles page: {}'.format(page_number)) # $HttpResponse
def xxs_positional_arg(request, arg0, arg1, no_taint=None): # $routeHandler $routedParameter=arg0 $routedParameter=arg1
return HttpResponse('xxs_positional_arg: {} {}'.format(arg0, arg1))
return HttpResponse('xxs_positional_arg: {} {}'.format(arg0, arg1)) # $HttpResponse
urlpatterns = [
@@ -63,7 +63,7 @@ urlpatterns = [
# Using patterns() for routing
def show_user(request, username): # $routeHandler $routedParameter=username
return HttpResponse('show_user {}'.format(username))
return HttpResponse('show_user {}'.format(username)) # $HttpResponse
urlpatterns = patterns(url(r"^users/(?P<username>[^/]+)", show_user)) # $routeSetup="^users/(?P<username>[^/]+)"
@@ -72,7 +72,7 @@ urlpatterns = patterns(url(r"^users/(?P<username>[^/]+)", show_user)) # $routeS
# Show we understand the keyword arguments to django.conf.urls.url
def kw_args(request): # $routeHandler
return HttpResponse('kw_args')
return HttpResponse('kw_args') # $HttpResponse
urlpatterns = [
url(view=kw_args, regex=r"^kw_args") # $routeSetup="^kw_args"

View File

@@ -1,2 +1,12 @@
import python
import experimental.meta.ConceptsTest
class DedicatedResponseTest extends HttpServerHttpResponseTest {
DedicatedResponseTest() { file.getShortName() = "response_test.py" }
}
class OtherResponseTest extends HttpServerHttpResponseTest {
OtherResponseTest() { not this instanceof DedicatedResponseTest }
override string getARelevantTag() { result = "HttpResponse" }
}

View File

@@ -5,20 +5,20 @@ from django.views import View
def url_match_xss(request, foo, bar, no_taint=None): # $routeHandler $routedParameter=foo $routedParameter=bar
return HttpResponse('url_match_xss: {} {}'.format(foo, bar))
return HttpResponse('url_match_xss: {} {}'.format(foo, bar)) # $HttpResponse
def get_params_xss(request): # $routeHandler
return HttpResponse(request.GET.get("untrusted"))
return HttpResponse(request.GET.get("untrusted")) # $HttpResponse
def post_params_xss(request): # $routeHandler
return HttpResponse(request.POST.get("untrusted"))
return HttpResponse(request.POST.get("untrusted")) # $HttpResponse
def http_resp_write(request): # $routeHandler
rsp = HttpResponse()
rsp.write(request.GET.get("untrusted"))
rsp = HttpResponse() # $HttpResponse
rsp.write(request.GET.get("untrusted")) # $HttpResponse
return rsp
@@ -27,22 +27,22 @@ class Foo(object):
def post(self, request, untrusted): # $f-:routeHandler $f-:routedParameter=untrusted
return HttpResponse('Foo post: {}'.format(untrusted))
return HttpResponse('Foo post: {}'.format(untrusted)) # $HttpResponse
class ClassView(View, Foo):
def get(self, request, untrusted): # $f-:routeHandler $f-:routedParameter=untrusted
return HttpResponse('ClassView get: {}'.format(untrusted))
return HttpResponse('ClassView get: {}'.format(untrusted)) # $HttpResponse
def show_articles(request, page_number=1): # $routeHandler $routedParameter=page_number
page_number = int(page_number)
return HttpResponse('articles page: {}'.format(page_number))
return HttpResponse('articles page: {}'.format(page_number)) # $HttpResponse
def xxs_positional_arg(request, arg0, arg1, no_taint=None): # $routeHandler $routedParameter=arg0 $routedParameter=arg1
return HttpResponse('xxs_positional_arg: {} {}'.format(arg0, arg1))
return HttpResponse('xxs_positional_arg: {} {}'.format(arg0, arg1)) # $HttpResponse
urlpatterns = [
@@ -63,7 +63,7 @@ urlpatterns = [
# Show we understand the keyword arguments to django.urls.re_path
def re_path_kwargs(request): # $routeHandler
return HttpResponse('re_path_kwargs')
return HttpResponse('re_path_kwargs') # $HttpResponse
urlpatterns = [
@@ -76,16 +76,16 @@ urlpatterns = [
# saying page_number is an externally controlled *string* is a bit strange, when we have an int converter :O
def page_number(request, page_number=1): # $routeHandler $routedParameter=page_number
return HttpResponse('page_number: {}'.format(page_number))
return HttpResponse('page_number: {}'.format(page_number)) # $HttpResponse
def foo_bar_baz(request, foo, bar, baz): # $routeHandler $routedParameter=foo $routedParameter=bar $routedParameter=baz
return HttpResponse('foo_bar_baz: {} {} {}'.format(foo, bar, baz))
return HttpResponse('foo_bar_baz: {} {} {}'.format(foo, bar, baz)) # $HttpResponse
def path_kwargs(request, foo, bar): # $routeHandler $routedParameter=foo $routedParameter=bar
return HttpResponse('path_kwargs: {} {} {}'.format(foo, bar))
return HttpResponse('path_kwargs: {} {} {}'.format(foo, bar)) # $HttpResponse
def not_valid_identifier(request): # $routeHandler
return HttpResponse('<foo!>')
return HttpResponse('<foo!>') # $HttpResponse
urlpatterns = [
path("articles/", page_number), # $routeSetup="articles/"
@@ -102,7 +102,7 @@ urlpatterns = [
from django.conf.urls import url
def deprecated(request): # $routeHandler
return HttpResponse('deprecated')
return HttpResponse('deprecated') # $HttpResponse
urlpatterns = [
url(r"^deprecated/", deprecated), # $routeSetup="^deprecated/"

View File

@@ -1,10 +1,10 @@
from django.http import HttpRequest, HttpResponse
def foo(request: HttpRequest): # $routeHandler
return HttpResponse("foo")
return HttpResponse("foo") # $HttpResponse
def bar_baz(request: HttpRequest): # $routeHandler
return HttpResponse("bar_baz")
return HttpResponse("bar_baz") # $HttpResponse
def deprecated(request: HttpRequest): # $routeHandler
return HttpResponse("deprecated")
return HttpResponse("deprecated") # $HttpResponse

View File

@@ -1,2 +1,12 @@
import python
import experimental.meta.ConceptsTest
class DedicatedResponseTest extends HttpServerHttpResponseTest {
DedicatedResponseTest() { file.getShortName() = "response_test.py" }
}
class OtherResponseTest extends HttpServerHttpResponseTest {
OtherResponseTest() { not this instanceof DedicatedResponseTest }
override string getARelevantTag() { result = "HttpResponse" }
}

View File

@@ -5,7 +5,7 @@ app = Flask(__name__)
@app.route("/") # $routeSetup="/"
def hello_world(): # $routeHandler
return "Hello World!"
return "Hello World!" # $HttpResponse
from flask.views import MethodView
@@ -26,42 +26,42 @@ app.add_url_rule('/the/', defaults={'user_id': None}, # $routeSetup="/the/"
@app.route("/dangerous") # $routeSetup="/dangerous"
def dangerous(): # $routeHandler
return request.args.get('payload')
return request.args.get('payload') # $HttpResponse
@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
return request.form['param1'] # $HttpResponse
return None # $f+:HttpResponse
@app.route("/unsafe") # $routeSetup="/unsafe"
def unsafe(): # $routeHandler
first_name = request.args.get('name', '')
return make_response("Your name is " + first_name)
return make_response("Your name is " + first_name) # $HttpResponse
@app.route("/safe") # $routeSetup="/safe"
def safe(): # $routeHandler
first_name = request.args.get('name', '')
return make_response("Your name is " + escape(first_name))
return make_response("Your name is " + escape(first_name)) # $HttpResponse
@app.route("/hello/<name>") # $routeSetup="/hello/<name>"
def hello(name): # $routeHandler $routedParameter=name
return make_response("Your name is " + name)
return make_response("Your name is " + name) # $HttpResponse
@app.route("/foo/<path:subpath>") # $routeSetup="/foo/<path:subpath>"
def foo(subpath): # $routeHandler $routedParameter=subpath
return make_response("The subpath is " + subpath)
return make_response("The subpath is " + subpath) # $HttpResponse
@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))
return make_response("foo={!r} bar={!r}".format(foo, bar)) # $HttpResponse
@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))
return make_response("lang_code {}".format(lang_code)) # $HttpResponse
if __name__ == "__main__":
app.run(debug=True)

View File

@@ -0,0 +1,179 @@
import json
from flask import Flask, make_response, jsonify, Response, request
app = Flask(__name__)
@app.route("/html1") # $routeSetup="/html1"
def html1(): # $routeHandler
return "<h1>hello</h1>" # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
@app.route("/html2") # $routeSetup="/html2"
def html2(): # $routeHandler
# note that response saved in a variable intentionally -- we wan the annotations to
# show that we recognize the response creation, and not the return (hopefully). (and
# do the same in the following of the file)
resp = make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/html3") # $routeSetup="/html3"
def html3(): # $routeHandler
resp = app.make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
# TODO: Create test-cases for the many ways that `make_response` can be used
# https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
@app.route("/html4") # $routeSetup="/html4"
def html4(): # $routeHandler
resp = Response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/html5") # $routeSetup="/html5"
def html5(): # $routeHandler
# note: flask.Flask.response_class is set to `flask.Response` by default.
# it can be overridden, but we don't try to handle that right now.
resp = Flask.response_class("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/html6") # $routeSetup="/html6"
def html6(): # $routeHandler
# note: app.response_class (flask.Flask.response_class) is set to `flask.Response` by default.
# it can be overridden, but we don't try to handle that right now.
resp = app.response_class("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/html7") # $routeSetup="/html7"
def html7(): # $routeHandler
resp = make_response() # $HttpResponse $mimetype=text/html
resp.set_data("<h1>hello</h1>") # $f-:responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/jsonify") # $routeSetup="/jsonify"
def jsonify_route(): # $routeHandler
data = {"foo": "bar"}
resp = jsonify(data) # $f-:HttpResponse $f-:mimetype=application/json $f-:responseBody=data
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
################################################################################
# Tricky return handling
################################################################################
@app.route("/tricky-return1") # $routeSetup="/tricky-return1"
def tricky_return1(): # $routeHandler
if "raw" in request.args:
resp = "<h1>hellu</h1>"
else:
resp = make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
return resp # $HttpResponse $mimetype=text/html $responseBody=resp
def helper():
if "raw" in request.args:
return "<h1>hellu</h1>"
else:
return make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
@app.route("/tricky-return2") # $routeSetup="/tricky-return2"
def tricky_return2(): # $routeHandler
resp = helper()
return resp # $HttpResponse $mimetype=text/html $responseBody=resp
################################################################################
# Setting content-type manually
################################################################################
@app.route("/content-type/response-modification1") # $routeSetup="/content-type/response-modification1"
def response_modification1(): # $routeHandler
resp = make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
resp.content_type = "text/plain" # $f-:HttpResponse $f-:mimetype=text/plain
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/content-type/response-modification2") # $routeSetup="/content-type/response-modification2"
def response_modification2(): # $routeHandler
resp = make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
resp.headers["content-type"] = "text/plain" # $f-:HttpResponse $f-:mimetype=text/plain
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
# Exploration of mimetype/content_type/headers arguments to `app.response_class` -- things can get tricky
# see https://werkzeug.palletsprojects.com/en/1.0.x/wrappers/#werkzeug.wrappers.Response
@app.route("/content-type/Response1") # $routeSetup="/content-type/Response1"
def Response1(): # $routeHandler
resp = Response("<h1>hello</h1>", mimetype="text/plain") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/content-type/Response2") # $routeSetup="/content-type/Response2"
def Response2(): # $routeHandler
resp = Response("<h1>hello</h1>", content_type="text/plain; charset=utf-8") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/content-type/Response3") # $routeSetup="/content-type/Response3"
def Response3(): # $routeHandler
# content_type argument takes priority (and result is text/plain)
resp = Response("<h1>hello</h1>", content_type="text/plain; charset=utf-8", mimetype="text/html") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/content-type/Response4") # $routeSetup="/content-type/Response4"
def Response4(): # $routeHandler
# note: capitalization of Content-Type does not matter
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/plain"}) # $HttpResponse $f+:mimetype=text/html $f-:mimetype=text/plain $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/content-type/Response5") # $routeSetup="/content-type/Response5"
def Response5(): # $routeHandler
# content_type argument takes priority (and result is text/plain)
# note: capitalization of Content-Type does not matter
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, content_type="text/plain; charset=utf-8") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/content-type/Response6") # $routeSetup="/content-type/Response6"
def Response6(): # $routeHandler
# mimetype argument takes priority over header (and result is text/plain)
# note: capitalization of Content-Type does not matter
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, mimetype="text/plain") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/content-type/Flask-response-class") # $routeSetup="/content-type/Flask-response-class"
def Flask_response_class(): # $routeHandler
# note: flask.Flask.response_class is set to `flask.Response` by default.
# it can be overridden, but we don't try to handle that right now.
resp = Flask.response_class("<h1>hello</h1>", mimetype="text/plain") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
@app.route("/content-type/app-response-class") # $routeSetup="/content-type/app-response-class"
def app_response_class(): # $routeHandler
# note: app.response_class (flask.Flask.response_class) is set to `flask.Response` by default.
# it can be overridden, but we don't try to handle that right now.
resp = app.response_class("<h1>hello</h1>", mimetype="text/plain") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
return resp # $f+:HttpResponse $f+:mimetype=text/html $f+:responseBody=resp
# TODO: add tests for setting status code
# TODO: add test that manually creates a redirect by setting status code and suitable header.
################################################################################
if __name__ == "__main__":
app.run(debug=True)

View File

@@ -7,24 +7,24 @@ app = Flask(__name__)
SOME_ROUTE = "/some/route"
@app.route(SOME_ROUTE) # $routeSetup="/some/route"
def some_route(): # $routeHandler
return make_response("some_route")
return make_response("some_route") # $HttpResponse
def index(): # $routeHandler
return make_response("index")
return make_response("index") # $HttpResponse
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")
return make_response("later_set") # $HttpResponse
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")
return make_response("unkown_route") # $HttpResponse
if __name__ == "__main__":

View File

@@ -200,7 +200,7 @@ def debug(foo, bar): # $routeHandler $routedParameter=foo $routedParameter=bar
print("request.pragma {!r}".format(request.pragma))
return 'ok'
return 'ok' # $HttpResponse
@app.route("/stream", methods=['POST']) # $routeSetup="/stream"
def stream(): # $routeHandler
@@ -210,7 +210,7 @@ def stream(): # $routeHandler
# just works :)
print(s.read())
return 'ok'
return 'ok' # $HttpResponse
@app.route("/input_stream", methods=['POST']) # $routeSetup="/input_stream"
def input_stream(): # $routeHandler
@@ -221,14 +221,14 @@ def input_stream(): # $routeHandler
# be handled manually
print(s.read())
return 'ok'
return 'ok' # $HttpResponse
@app.route("/form", methods=['POST']) # $routeSetup="/form"
def form(): # $routeHandler
print(request.path)
print("request.form", request.form)
return 'ok'
return 'ok' # $HttpResponse
@app.route("/cache_control", methods=['POST']) # $routeSetup="/cache_control"
def cache_control(): # $routeHandler
@@ -237,7 +237,7 @@ def cache_control(): # $routeHandler
print("request.cache_control.max_stale", request.cache_control.max_stale, type(request.cache_control.max_stale))
print("request.cache_control.min_fresh", request.cache_control.min_fresh, type(request.cache_control.min_fresh))
return 'ok'
return 'ok' # $HttpResponse
@app.route("/file_upload", methods=['POST']) # $routeSetup="/file_upload"
def file_upload(): # $routeHandler
@@ -245,14 +245,14 @@ def file_upload(): # $routeHandler
for k,v in request.files.items():
print(k, v, v.name, v.filename, v.stream)
return 'ok'
return 'ok' # $HttpResponse
@app.route("/args", methods=['GET']) # $routeSetup="/args"
def args(): # $routeHandler
print(request.path)
print("request.args", request.args)
return 'ok'
return 'ok' # $HttpResponse
# curl --header "My-Header: some-value" http://localhost:5000/debug/fooval/barval
# curl --header "Pragma: foo, bar" --header "Pragma: stuff, foo" http://localhost:5000/debug/fooval/barval

View File

@@ -143,6 +143,48 @@ class HttpServerRouteSetupTest extends InlineExpectationsTest {
}
}
class HttpServerHttpResponseTest extends InlineExpectationsTest {
File file;
HttpServerHttpResponseTest() {
file.getExtension() = "py" and
this = "HttpServerHttpResponseTest: " + file
}
override string getARelevantTag() { result in ["HttpResponse", "responseBody", "mimetype"] }
override predicate hasActualResult(Location location, string element, string tag, string value) {
// By adding `file` as a class field, and these two restrictions, it's possible to
// say that we only want to check _some_ tags for certain files. This helped make
// flask tests more readable since adding full annotations for HttpResponses in the
// the tests for routing setup is both annoying and not very useful.
location.getFile() = file and
tag = getARelevantTag() and
(
exists(HTTP::Server::HttpResponse response |
location = response.getLocation() and
element = response.toString() and
value = "" and
tag = "HttpResponse"
)
or
exists(HTTP::Server::HttpResponse response |
location = response.getLocation() and
element = response.toString() and
value = value_from_expr(response.getBody().asExpr()) and
tag = "responseBody"
)
or
exists(HTTP::Server::HttpResponse response |
location = response.getLocation() and
element = response.toString() and
value = response.getMimetype() and
tag = "mimetype"
)
)
}
}
class FileSystemAccessTest extends InlineExpectationsTest {
FileSystemAccessTest() { this = "FileSystemAccessTest" }

View File

@@ -0,0 +1,7 @@
edges
| reflected_xss.py:8:18:8:29 | ControlFlowNode for Attribute | reflected_xss.py:9:26:9:53 | ControlFlowNode for BinaryExpr |
nodes
| reflected_xss.py:8:18:8:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| reflected_xss.py:9:26:9:53 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
#select
| reflected_xss.py:9:26:9:53 | ControlFlowNode for BinaryExpr | reflected_xss.py:8:18:8:29 | ControlFlowNode for Attribute | reflected_xss.py:9:26:9:53 | ControlFlowNode for BinaryExpr | Cross-site scripting vulnerability due to $@. | reflected_xss.py:8:18:8:29 | ControlFlowNode for Attribute | a user-provided value |

View File

@@ -0,0 +1 @@
experimental/Security-new-dataflow/CWE-079/ReflectedXss.ql

View File

@@ -0,0 +1,15 @@
from flask import Flask, request, make_response, escape
app = Flask(__name__)
@app.route("/unsafe")
def unsafe():
first_name = request.args.get("name", "")
return make_response("Your name is " + first_name) # NOT OK
@app.route("/safe")
def safe():
first_name = request.args.get("name", "")
return make_response("Your name is " + escape(first_name)) # OK