Python: Add basic taint modeling of tornado request

This commit is contained in:
Rasmus Wriedt Larsen
2020-12-22 14:37:51 +01:00
parent 4641150d45
commit 39d85896a1
2 changed files with 162 additions and 20 deletions

View File

@@ -33,7 +33,7 @@ private module Tornado {
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node tornado_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["web"] and
attr_name in ["web", "httputil"] and
(
t.start() and
result = DataFlow::importNode("tornado" + "." + attr_name)
@@ -221,6 +221,148 @@ private module Tornado {
)
}
}
private class RequestAttrAccess extends tornado::httputil::HttpServerRequest::InstanceSource {
RequestAttrAccess() {
this.(DataFlow::AttrRead).getObject() = instance() and
this.(DataFlow::AttrRead).getAttributeName() = "request"
}
}
}
}
// -------------------------------------------------------------------------
// tornado.httputil
// -------------------------------------------------------------------------
/** Gets a reference to the `tornado.httputil` module. */
DataFlow::Node httputil() { result = tornado_attr("httputil") }
/** Provides models for the `tornado.httputil` module */
module httputil {
/**
* Gets a reference to the attribute `attr_name` of the `tornado.httputil` module.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node httputil_attr(DataFlow::TypeTracker t, string attr_name) {
attr_name in ["HTTPServerRequest"] and
(
t.start() and
result = DataFlow::importNode("tornado.httputil" + "." + attr_name)
or
t.startInAttr(attr_name) and
result = httputil()
)
or
// Due to bad performance when using normal setup with `httputil_attr(t2, attr_name).track(t2, t)`
// we have inlined that code and forced a join
exists(DataFlow::TypeTracker t2 |
exists(DataFlow::StepSummary summary |
httputil_attr_first_join(t2, attr_name, result, summary) and
t = t2.append(summary)
)
)
}
pragma[nomagic]
private predicate httputil_attr_first_join(
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
DataFlow::StepSummary summary
) {
DataFlow::StepSummary::step(httputil_attr(t2, attr_name), res, summary)
}
/**
* Gets a reference to the attribute `attr_name` of the `tornado.httputil` module.
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node httputil_attr(string attr_name) {
result = httputil_attr(DataFlow::TypeTracker::end(), attr_name)
}
/**
* Provides models for the `tornado.httputil.HttpServerRequest` class
*
* See https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest.
*/
module HttpServerRequest {
/** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
t.start() and
result = httputil_attr("HttpServerRequest")
or
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
}
/** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
/**
* A source of instances of `tornado.httputil.HttpServerRequest`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `HttpServerRequest::instance()` to get references to instances of `tornado.httputil.HttpServerRequest`.
*/
abstract class InstanceSource extends DataFlow::Node { }
/** A direct instantiation of `tornado.httputil.HttpServerRequest`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
override CallNode node;
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
}
/** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */
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 `tornado.httputil.HttpServerRequest`. */
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
/** Gets a reference to the `full_url` method. */
private DataFlow::Node full_url(DataFlow::TypeTracker t) {
t.startInAttr("full_url") and
result = instance()
or
exists(DataFlow::TypeTracker t2 | result = full_url(t2).track(t2, t))
}
/** Gets a reference to the `full_url` method. */
DataFlow::Node full_url() { result = full_url(DataFlow::TypeTracker::end()) }
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Method access
nodeTo.(DataFlow::AttrRead).getObject() = nodeFrom and
nodeFrom = instance() and
nodeTo in [full_url()]
or
// Method call
nodeTo.asCfgNode().(CallNode).getFunction() = nodeFrom.asCfgNode() and
nodeFrom in [full_url()]
or
// Attributes
nodeFrom = instance() and
exists(DataFlow::AttrRead read | nodeTo = read and read.getObject() = nodeFrom |
read.getAttributeName() in [
// str / bytes
"uri", "path", "query", "remote_ip", "body",
// Dict[str, List[bytes]]
"arguments", "query_arguments", "body_arguments",
// dict-like, https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPHeaders
"headers",
// Dict[str, http.cookies.Morsel]
"cookies"
]
)
}
}
}
}
}

View File

@@ -15,27 +15,27 @@
| taint_test.py:26 | ok | get | self.path_kwargs |
| taint_test.py:27 | ok | get | self.path_kwargs["name"] |
| taint_test.py:34 | ok | get | request |
| taint_test.py:36 | fail | get | request.uri |
| taint_test.py:37 | fail | get | request.path |
| taint_test.py:38 | fail | get | request.query |
| taint_test.py:39 | fail | get | request.full_url() |
| taint_test.py:41 | fail | get | request.remote_ip |
| taint_test.py:43 | fail | get | request.body |
| taint_test.py:45 | fail | get | request.arguments |
| taint_test.py:46 | fail | get | request.arguments["name"] |
| taint_test.py:47 | fail | get | request.arguments["name"][0] |
| taint_test.py:49 | fail | get | request.query_arguments |
| taint_test.py:50 | fail | get | request.query_arguments["name"] |
| taint_test.py:51 | fail | get | request.query_arguments["name"][0] |
| taint_test.py:53 | fail | get | request.body_arguments |
| taint_test.py:54 | fail | get | request.body_arguments["name"] |
| taint_test.py:55 | fail | get | request.body_arguments["name"][0] |
| taint_test.py:58 | fail | get | request.headers |
| taint_test.py:59 | fail | get | request.headers["header-name"] |
| taint_test.py:36 | ok | get | request.uri |
| taint_test.py:37 | ok | get | request.path |
| taint_test.py:38 | ok | get | request.query |
| taint_test.py:39 | ok | get | request.full_url() |
| taint_test.py:41 | ok | get | request.remote_ip |
| taint_test.py:43 | ok | get | request.body |
| taint_test.py:45 | ok | get | request.arguments |
| taint_test.py:46 | ok | get | request.arguments["name"] |
| taint_test.py:47 | ok | get | request.arguments["name"][0] |
| taint_test.py:49 | ok | get | request.query_arguments |
| taint_test.py:50 | ok | get | request.query_arguments["name"] |
| taint_test.py:51 | ok | get | request.query_arguments["name"][0] |
| taint_test.py:53 | ok | get | request.body_arguments |
| taint_test.py:54 | ok | get | request.body_arguments["name"] |
| taint_test.py:55 | ok | get | request.body_arguments["name"][0] |
| taint_test.py:58 | ok | get | request.headers |
| taint_test.py:59 | ok | get | request.headers["header-name"] |
| taint_test.py:60 | fail | get | request.headers.get_list(..) |
| taint_test.py:61 | fail | get | request.headers.get_all() |
| taint_test.py:62 | fail | get | ListComp |
| taint_test.py:65 | fail | get | request.cookies |
| taint_test.py:66 | fail | get | request.cookies["cookie-name"] |
| taint_test.py:65 | ok | get | request.cookies |
| taint_test.py:66 | ok | get | request.cookies["cookie-name"] |
| taint_test.py:67 | fail | get | request.cookies["cookie-name"].key |
| taint_test.py:68 | fail | get | request.cookies["cookie-name"].value |