Skip to content

Commit 1336321

Browse files
committed
Python: Model werkzeug Headers
Also removed a misleading comment link to method on wrong class :D
1 parent 4d9c86a commit 1336321

File tree

3 files changed

+62
-4
lines changed

3 files changed

+62
-4
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,13 @@ module Flask {
432432
}
433433
}
434434

435+
/** An `Headers` instance that originates from a flask request. */
436+
private class FlaskRequestHeadersInstances extends Werkzeug::Headers::InstanceSource {
437+
FlaskRequestHeadersInstances() {
438+
this.(DataFlow::AttrRead).accesses(request().getAUse(), "headers")
439+
}
440+
}
441+
435442
// ---------------------------------------------------------------------------
436443
// Implicit response from returns of flask request handlers
437444
// ---------------------------------------------------------------------------

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ module Werkzeug {
4949

5050
private class MultiDictAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
5151
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
52-
// See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist
5352
nodeFrom = instance() and
5453
nodeTo.(DataFlow::MethodCallNode).calls(nodeFrom, "getlist")
5554
}
@@ -122,6 +121,54 @@ module Werkzeug {
122121
}
123122
}
124123

124+
/**
125+
* Provides models for the `werkzeug.datastructures.Headers` class
126+
*
127+
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.
128+
*/
129+
module Headers {
130+
/**
131+
* A source of instances of `werkzeug.datastructures.Headers`, extend this class to model new instances.
132+
*
133+
* This can include instantiations of the class, return values from function
134+
* calls, or a special parameter that will be set when functions are called by an external
135+
* library.
136+
*
137+
* Use the predicate `Headers::instance()` to get references to instances of `werkzeug.datastructures.Headers`.
138+
*/
139+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
140+
141+
/** Gets a reference to an instance of `werkzeug.datastructures.Headers`. */
142+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
143+
t.start() and
144+
result instanceof InstanceSource
145+
or
146+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
147+
}
148+
149+
/** Gets a reference to an instance of `werkzeug.datastructures.Headers`. */
150+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
151+
152+
/**
153+
* Taint propagation for `werkzeug.datastructures.Headers`.
154+
*/
155+
class HeadersAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
156+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
157+
// Methods
158+
//
159+
// TODO: When we have tools that make it easy, model these properly to handle
160+
// `meth = obj.meth; meth()`. Until then, we'll use this more syntactic approach
161+
// (since it allows us to at least capture the most common cases).
162+
nodeFrom = instance() and
163+
exists(DataFlow::AttrRead attr | attr.getObject() = nodeFrom |
164+
// methods (non-async)
165+
attr.getAttributeName() in ["getlist", "get_all", "popitem", "to_wsgi_list"] and
166+
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr
167+
)
168+
}
169+
}
170+
}
171+
125172
import WerkzeugOld
126173
}
127174

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,15 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route
9696
request.headers, # $ tainted
9797
request.headers['key'], # $ tainted
9898
request.headers.get('key'), # $ tainted
99-
request.headers.get_all('key'), # $ MISSING: tainted
100-
request.headers.getlist('key'), # $ MISSING: tainted
99+
request.headers.get_all('key'), # $ tainted
100+
request.headers.getlist('key'), # $ tainted
101+
# popitem returns `(key, value)`
102+
request.headers.popitem(), # $ tainted
103+
request.headers.popitem()[0], # $ tainted
104+
request.headers.popitem()[1], # $ tainted
101105
# two ways to get (k, v) lists
102106
list(request.headers), # $ tainted
103-
request.headers.to_wsgi_list(), # $ MISSING: tainted
107+
request.headers.to_wsgi_list(), # $ tainted
104108

105109
request.json, # $ tainted
106110
request.json['foo'], # $ tainted

0 commit comments

Comments
 (0)