Skip to content

Commit d953ea4

Browse files
committed
Python: Basic handling of tainted attributes in aiohttp
1 parent 88158e7 commit d953ea4

File tree

2 files changed

+65
-38
lines changed

2 files changed

+65
-38
lines changed

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,37 @@ module AiohttpWebModel {
197197
// ```
198198
this.getParameter() =
199199
max(Parameter param, int i | param = requestHandler.getArg(i) | param order by i)
200-
201200
)
202201
}
203202

204203
override string getSourceType() { result = "aiohttp.web.Request" }
205204
}
205+
206+
/**
207+
* Taint propagation for `aiohttp.web.Request`.
208+
*
209+
* See https://docs.aiohttp.org/en/stable/web_reference.html#request-and-base-request
210+
*/
211+
private class AiohttpRequestAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
212+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
213+
// Methods
214+
exists(string method_name | method_name in ["TODO"] |
215+
// Method access (obj -> obj.meth)
216+
none()
217+
or
218+
// Method call (obj.meth -> obj.meth())
219+
none()
220+
)
221+
or
222+
// Attributes
223+
nodeFrom = Request::instance() and
224+
nodeTo.(DataFlow::AttrRead).getObject() = nodeFrom and
225+
nodeTo.(DataFlow::AttrRead).getAttributeName() in [
226+
"url", "rel_url", "forwarded", "host", "remote", "path", "path_qs", "raw_path", "query",
227+
"headers", "transport", "cookies", "content", "_payload", "body_exists", "has_body",
228+
"content_type", "charset", "http_range", "if_modified_since", "if_unmodified_since",
229+
"if_range"
230+
]
231+
}
232+
}
206233
}

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

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,76 +8,76 @@ async def test_taint(request: web.Request): # $ requestHandler
88
# yarl.URL instances
99
# https://yarl.readthedocs.io/en/stable/api.html#yarl.URL
1010
# see below
11-
request.url, # $ MISSING: tainted
12-
request.rel_url, # $ MISSING: tainted
11+
request.url, # $ tainted
12+
request.rel_url, # $ tainted
1313

14-
request.forwarded, # $ MISSING: tainted
14+
request.forwarded, # $ tainted
1515

16-
request.host, # $ MISSING: tainted
17-
request.remote, # $ MISSING: tainted
18-
request.path, # $ MISSING: tainted
19-
request.path_qs, # $ MISSING: tainted
20-
request.raw_path, # $ MISSING: tainted
16+
request.host, # $ tainted
17+
request.remote, # $ tainted
18+
request.path, # $ tainted
19+
request.path_qs, # $ tainted
20+
request.raw_path, # $ tainted
2121

2222
# multidict.MultiDictProxy[str]
2323
# see https://multidict.readthedocs.io/en/stable/multidict.html#multidict.MultiDictProxy
2424
# TODO: Should have a better way to capture that we in fact _do_ model this as a
2525
# an instance of the right class, and have the actual taint_test for that in a
2626
# different file!
27-
request.query, # $ MISSING: tainted
28-
request.query["key"], # $ MISSING: tainted
29-
request.query.get("key"), # $ MISSING: tainted
27+
request.query, # $ tainted
28+
request.query["key"], # $ tainted
29+
request.query.get("key"), # $ tainted
3030
request.query.getone("key"), # $ MISSING: tainted
3131
request.query.getall("key"), # $ MISSING: tainted
3232
request.query.keys(), # $ MISSING: tainted
33-
request.query.values(), # $ MISSING: tainted
34-
request.query.items(), # $ MISSING: tainted
35-
request.query.copy(), # $ MISSING: tainted
36-
list(request.query), # $ MISSING: tainted
37-
iter(request.query), # $ MISSING: tainted
33+
request.query.values(), # $ tainted
34+
request.query.items(), # $ tainted
35+
request.query.copy(), # $ tainted
36+
list(request.query), # $ tainted
37+
iter(request.query), # $ tainted
3838

3939
# multidict.CIMultiDictProxy[str]
4040
# see https://multidict.readthedocs.io/en/stable/multidict.html#multidict.CIMultiDictProxy
4141
# TODO: Should have a better way to capture that we in fact _do_ model this as a
4242
# an instance of the right class, and have the actual taint_test for that in a
4343
# different file!
44-
request.headers, # $ MISSING: tainted
45-
request.query.getone("key"), # $ MISSING: tainted
44+
request.headers, # $ tainted
45+
request.headers.getone("key"), # $ MISSING: tainted
4646

4747
# https://docs.python.org/3/library/asyncio-protocol.html#asyncio-transport
4848
# TODO
49-
request.transport, # $ MISSING: tainted
49+
request.transport, # $ tainted
5050
request.transport.get_extra_info("key"), # $ MISSING: tainted
5151

5252
# dict-like (readonly)
53-
request.cookies, # $ MISSING: tainted
54-
request.cookies["key"], # $ MISSING: tainted
55-
request.cookies.get("key"), # $ MISSING: tainted
53+
request.cookies, # $ tainted
54+
request.cookies["key"], # $ tainted
55+
request.cookies.get("key"), # $ tainted
5656
request.cookies.keys(), # $ MISSING: tainted
57-
request.cookies.values(), # $ MISSING: tainted
58-
request.cookies.items(), # $ MISSING: tainted
59-
list(request.cookies), # $ MISSING: tainted
60-
iter(request.cookies), # $ MISSING: tainted
57+
request.cookies.values(), # $ tainted
58+
request.cookies.items(), # $ tainted
59+
list(request.cookies), # $ tainted
60+
iter(request.cookies), # $ tainted
6161

6262

6363
# aiohttp.StreamReader
6464
# see https://docs.aiohttp.org/en/stable/streams.html#aiohttp.StreamReader
6565
# TODO
66-
request.content, # $ MISSING: tainted
67-
request._payload, # $ MISSING: tainted
66+
request.content, # $ tainted
67+
request._payload, # $ tainted
6868

69-
request.body_exists, # $ MISSING: tainted
70-
request.has_body, # $ MISSING: tainted
69+
request.body_exists, # $ tainted
70+
request.has_body, # $ tainted
7171

72-
request.content_type, # $ MISSING: tainted
73-
request.charset, # $ MISSING: tainted
72+
request.content_type, # $ tainted
73+
request.charset, # $ tainted
7474

75-
request.http_range, # $ MISSING: tainted
75+
request.http_range, # $ tainted
7676

7777
# Optional[datetime]
78-
request.if_modified_since, # $ MISSING: tainted
79-
request.if_unmodified_since, # $ MISSING: tainted
80-
request.if_range, # $ MISSING: tainted
78+
request.if_modified_since, # $ tainted
79+
request.if_unmodified_since, # $ tainted
80+
request.if_range, # $ tainted
8181

8282
request.clone(scheme="https"), # $ MISSING: tainted
8383

@@ -182,7 +182,7 @@ async def test_taint(request: web.Request): # $ requestHandler
182182
request.url.with_fragment("foo"), # $ MISSING: tainted
183183
request.url.with_name("foo"), # $ MISSING: tainted
184184

185-
request.url.join(yarl.URL("wat.html")), # $ MISSING: tainted
185+
request.url.join(yarl.URL("wat.html")), # $ tainted
186186

187187
request.url.human_repr(), # $ MISSING: tainted
188188
)

0 commit comments

Comments
 (0)