Skip to content

Commit 7e09a1c

Browse files
committed
Python: Model tornado.httputil.HTTPHeaders
1 parent 7020e41 commit 7e09a1c

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

python/ql/src/semmle/python/frameworks/Tornado.qll

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,54 @@ private import semmle.python.regex
1616
* See https://www.tornadoweb.org/en/stable/.
1717
*/
1818
private module Tornado {
19+
/**
20+
* Provides models for the `tornado.httputil.HTTPHeaders` class
21+
*
22+
* See https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPHeaders.
23+
*/
24+
module HTTPHeaders {
25+
/**
26+
* A source of instances of `tornado.httputil.HTTPHeaders`, extend this class to model new instances.
27+
*
28+
* This can include instantiations of the class, return values from function
29+
* calls, or a special parameter that will be set when functions are called by an external
30+
* library.
31+
*
32+
* Use the predicate `HTTPHeaders::instance()` to get references to instances of `tornado.httputil.HTTPHeaders`.
33+
*/
34+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
35+
36+
/** Gets a reference to an instance of `tornado.httputil.HTTPHeaders`. */
37+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
38+
t.start() and
39+
result instanceof InstanceSource
40+
or
41+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
42+
}
43+
44+
/** Gets a reference to an instance of `tornado.httputil.HTTPHeaders`. */
45+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
46+
47+
/**
48+
* Taint propagation for `tornado.httputil.HTTPHeaders`.
49+
*/
50+
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
51+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
52+
// Methods
53+
//
54+
// TODO: When we have tools that make it easy, model these properly to handle
55+
// `meth = obj.meth; meth()`. Until then, we'll use this more syntactic approach
56+
// (since it allows us to at least capture the most common cases).
57+
nodeFrom = instance() and
58+
exists(DataFlow::AttrRead attr | attr.getObject() = nodeFrom |
59+
// normal (non-async) methods
60+
attr.getAttributeName() in ["get_list", "get_all"] and
61+
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr
62+
)
63+
}
64+
}
65+
}
66+
1967
// ---------------------------------------------------------------------------
2068
// tornado
2169
// ---------------------------------------------------------------------------
@@ -312,6 +360,13 @@ private module Tornado {
312360
)
313361
}
314362
}
363+
364+
/** An `HTTPHeaders` instance that originates from a Tornado request. */
365+
private class TornadoRequestHTTPHeadersInstances extends HTTPHeaders::InstanceSource {
366+
TornadoRequestHTTPHeadersInstances() {
367+
this.(DataFlow::AttrRead).accesses(instance(), "headers")
368+
}
369+
}
315370
}
316371
}
317372
}

python/ql/test/library-tests/frameworks/tornado/taint_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ def get(self, name = "World!", number="0", foo="foo"): # $ requestHandler route
6161
# dict-like, see https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPHeaders
6262
request.headers, # $ tainted
6363
request.headers["header-name"], # $ tainted
64-
request.headers.get_list("header-name"), # $ MISSING: tainted
65-
request.headers.get_all(), # $ MISSING: tainted
64+
request.headers.get_list("header-name"), # $ tainted
65+
request.headers.get_all(), # $ tainted
6666
[(k, v) for (k, v) in request.headers.get_all()], # $ MISSING: tainted
6767

6868
# Dict[str, http.cookies.Morsel]

0 commit comments

Comments
 (0)