Skip to content

Commit 9cd8a86

Browse files
committed
Python: Expand Tornado tests and add annotations
I should probably have split this up into 2 commits, so sorry that didn't happen :|
1 parent b4f3399 commit 9cd8a86

File tree

10 files changed

+314
-13
lines changed

10 files changed

+314
-13
lines changed

python/ql/test/experimental/library-tests/frameworks/tornado/ConceptsTest.expected

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import python
2+
import experimental.meta.ConceptsTest
3+
//
4+
// class DedicatedResponseTest extends HttpServerHttpResponseTest {
5+
// DedicatedResponseTest() { file.getShortName() = "response_test.py" }
6+
// }
7+
//
8+
// class OtherResponseTest extends HttpServerHttpResponseTest {
9+
// OtherResponseTest() { not this instanceof DedicatedResponseTest }
10+
//
11+
// override string getARelevantTag() { result = "HttpResponse" }
12+
// }
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
From Tornado 6.0 Python 3.5+ is [required](https://www.tornadoweb.org/en/stable/index.html#installation)
2+
3+
https://www.tornadoweb.org/en/stable/guide/structure.html#handling-request-input
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
| taint_test.py:6 | fail | get | name |
2+
| taint_test.py:6 | fail | get | number |
3+
| taint_test.py:7 | ok | get | foo |
4+
| taint_test.py:11 | fail | get | self.get_argument(..) |
5+
| taint_test.py:12 | fail | get | self.get_arguments(..) |
6+
| taint_test.py:13 | fail | get | self.get_arguments(..)[0] |
7+
| taint_test.py:15 | fail | get | self.get_body_argument(..) |
8+
| taint_test.py:16 | fail | get | self.get_body_arguments(..) |
9+
| taint_test.py:17 | fail | get | self.get_body_arguments(..)[0] |
10+
| taint_test.py:19 | fail | get | self.get_query_argument(..) |
11+
| taint_test.py:20 | fail | get | self.get_query_arguments(..) |
12+
| taint_test.py:21 | fail | get | self.get_query_arguments(..)[0] |
13+
| taint_test.py:23 | fail | get | self.path_args |
14+
| taint_test.py:24 | fail | get | self.path_args[0] |
15+
| taint_test.py:26 | fail | get | self.path_kwargs |
16+
| taint_test.py:27 | fail | get | self.path_kwargs["name"] |
17+
| taint_test.py:34 | fail | get | request |
18+
| taint_test.py:36 | fail | get | request.uri |
19+
| taint_test.py:37 | fail | get | request.path |
20+
| taint_test.py:38 | fail | get | request.query |
21+
| taint_test.py:39 | fail | get | request.full_url() |
22+
| taint_test.py:41 | fail | get | request.remote_ip |
23+
| taint_test.py:43 | fail | get | request.body |
24+
| taint_test.py:45 | fail | get | request.arguments |
25+
| taint_test.py:46 | fail | get | request.arguments["name"] |
26+
| taint_test.py:47 | fail | get | request.arguments["name"][0] |
27+
| taint_test.py:49 | fail | get | request.query_arguments |
28+
| taint_test.py:50 | fail | get | request.query_arguments["name"] |
29+
| taint_test.py:51 | fail | get | request.query_arguments["name"][0] |
30+
| taint_test.py:53 | fail | get | request.body_arguments |
31+
| taint_test.py:54 | fail | get | request.body_arguments["name"] |
32+
| taint_test.py:55 | fail | get | request.body_arguments["name"][0] |
33+
| taint_test.py:58 | fail | get | request.headers |
34+
| taint_test.py:59 | fail | get | request.headers["header-name"] |
35+
| taint_test.py:60 | fail | get | request.headers.get_list(..) |
36+
| taint_test.py:61 | fail | get | request.headers.get_all() |
37+
| taint_test.py:62 | fail | get | ListComp |
38+
| taint_test.py:65 | fail | get | request.cookies |
39+
| taint_test.py:66 | fail | get | request.cookies["cookie-name"] |
40+
| taint_test.py:67 | fail | get | request.cookies["cookie-name"].key |
41+
| taint_test.py:68 | fail | get | request.cookies["cookie-name"].value |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import experimental.dataflow.tainttracking.TestTaintLib
2+
import semmle.python.dataflow.new.RemoteFlowSources
3+
4+
class RemoteFlowTestTaintConfiguration extends TestTaintTrackingConfiguration {
5+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
6+
}

python/ql/test/experimental/library-tests/frameworks/tornado/basic.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,34 @@
22

33

44
class BasicHandler(tornado.web.RequestHandler):
5-
def get(self):
5+
def get(self): # $ MISSING: requestHandler
66
self.write("BasicHandler " + self.get_argument("xss"))
77

8-
def post(self):
8+
def post(self): # $ MISSING: requestHandler
99
self.write("BasicHandler (POST)")
1010

1111

1212
class DeepInheritance(BasicHandler):
13-
def get(self):
13+
def get(self): # $ MISSING: requestHandler
1414
self.write("DeepInheritance" + self.get_argument("also_xss"))
1515

1616

1717
class FormHandler(tornado.web.RequestHandler):
18-
def post(self):
18+
def post(self): # $ MISSING: requestHandler
1919
name = self.get_body_argument("name")
2020
self.write(name)
2121

2222

2323
class RedirectHandler(tornado.web.RequestHandler):
24-
def get(self):
24+
def get(self): # $ MISSING: requestHandler
2525
req = self.request
2626
h = req.headers
2727
url = h["url"]
2828
self.redirect(url)
2929

3030

3131
class BaseReverseInheritance(tornado.web.RequestHandler):
32-
def get(self):
32+
def get(self): # $ MISSING: requestHandler
3333
self.write("hello from BaseReverseInheritance")
3434

3535

@@ -38,13 +38,16 @@ class ReverseInheritance(BaseReverseInheritance):
3838

3939

4040
def make_app():
41-
return tornado.web.Application([
42-
(r"/basic", BasicHandler),
43-
(r"/deep", DeepInheritance),
44-
(r"/form", FormHandler),
45-
(r"/redirect", RedirectHandler),
46-
(r"/reverse-inheritance", ReverseInheritance),
47-
])
41+
return tornado.web.Application(
42+
[
43+
(r"/basic", BasicHandler), # $ MISSING: routeSetup="/basic"
44+
(r"/deep", DeepInheritance), # $ MISSING: routeSetup="/deep"
45+
(r"/form", FormHandler), # $ MISSING: routeSetup="/form"
46+
(r"/redirect", RedirectHandler), # $ MISSING: routeSetup="/redirect"
47+
(r"/reverse-inheritance", ReverseInheritance), # $ MISSING: routeSetup="/reverse-inheritance"
48+
],
49+
debug=True,
50+
)
4851

4952

5053
if __name__ == "__main__":
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
semmle-extractor-options: --max-import-depth=1 --lang=3
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import tornado.web
2+
3+
4+
class ResponseWriting(tornado.web.RequestHandler):
5+
def get(self, type_): # $ MISSING: requestHandler routedParameter=type_
6+
if type_ == "str":
7+
self.write("foo")
8+
elif type_ == "bytes":
9+
self.write(b"foo")
10+
elif type_ == "dict":
11+
# Content-type will be set to `application/json`
12+
self.write({"foo": 42})
13+
else:
14+
raise Exception("Bad type {} {}".format(type_, type(type_)))
15+
16+
17+
def make_app():
18+
return tornado.web.Application(
19+
[
20+
(r"/ResponseWriting/(str|bytes|dict)", ResponseWriting), # $ MISSING: routeSetup="/ResponseWriting/(str|bytes|dict)"
21+
],
22+
debug=True,
23+
)
24+
25+
26+
if __name__ == "__main__":
27+
import tornado.ioloop
28+
29+
app = make_app()
30+
app.listen(8888)
31+
tornado.ioloop.IOLoop.current().start()
32+
33+
# http://localhost:8888/ResponseWriting/str
34+
# http://localhost:8888/ResponseWriting/bytes
35+
# http://localhost:8888/ResponseWriting/dict
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import tornado.web
2+
import tornado.routing
3+
4+
5+
class FooHandler(tornado.web.RequestHandler):
6+
def get(self, x, y=None, not_used=None): # $ MISSING: requestHandler routedParameter=x routedParameter=y
7+
self.write("FooHandler {} {}".format(x, y))
8+
9+
10+
class BarHandler(tornado.web.RequestHandler):
11+
def get(self, x, y=None, not_used=None): # $ MISSING: requestHandler routedParameter=x routedParameter=y
12+
self.write("BarHandler {} {}".format(x, y))
13+
14+
15+
class BazHandler(tornado.web.RequestHandler):
16+
def get(self, x, y=None, not_used=None): # $ MISSING: requestHandler routedParameter=x routedParameter=y
17+
self.write("BazHandler {} {}".format(x, y))
18+
19+
20+
class KwArgs(tornado.web.RequestHandler):
21+
def get(self, *, x, y=None, not_used=None): # $ MISSING: requestHandler routedParameter=x routedParameter=y
22+
self.write("KwArgs {} {}".format(x, y))
23+
24+
25+
class OnlyLocalhost(tornado.web.RequestHandler):
26+
def get(self): # $ MISSING: requestHandler
27+
self.write("OnlyLocalhost")
28+
29+
30+
class One(tornado.web.RequestHandler):
31+
def get(self): # $ MISSING: requestHandler
32+
self.write("One")
33+
34+
35+
class Two(tornado.web.RequestHandler):
36+
def get(self): # $ MISSING: requestHandler
37+
self.write("Two")
38+
39+
40+
class Three(tornado.web.RequestHandler):
41+
def get(self): # $ MISSING: requestHandler
42+
self.write("Three")
43+
44+
45+
class AddedLater(tornado.web.RequestHandler):
46+
def get(self, x, y=None, not_used=None): # $ MISSING: requestHandler routedParameter=x routedParameter=y
47+
self.write("AddedLater {} {}".format(x, y))
48+
49+
50+
class PossiblyNotRouted(tornado.web.RequestHandler):
51+
# Even if our analysis can't find a route-setup for this class, we should still
52+
# consider it to be a handle incoming HTTP requests
53+
54+
def get(self): # $ MISSING: requestHandler
55+
self.write("NotRouted")
56+
57+
58+
def make_app():
59+
# see https://www.tornadoweb.org/en/stable/routing.html for even more examples
60+
app = tornado.web.Application(
61+
[
62+
(r"/foo/([0-9]+)/([0-9]+)?", FooHandler), # $ MISSING: routeSetup="/foo/([0-9]+)/([0-9]+)?"
63+
tornado.web.URLSpec(r"/bar/([0-9]+)/([0-9]+)?", BarHandler), # $ MISSING: routeSetup="/bar/([0-9]+)/([0-9]+)?"
64+
# Very verbose way to write same as FooHandler
65+
tornado.routing.Rule(tornado.routing.PathMatches(r"/baz/([0-9]+)/([0-9]+)?"), BazHandler), # $ MISSING: routeSetup="/baz/([0-9]+)/([0-9]+)?"
66+
(r"/kw-args/(?P<x>[0-9]+)/(?P<y>[0-9]+)?", KwArgs), # $ MISSING: routeSetup="/kw-args/(?P<x>[0-9]+)/(?P<y>[0-9]+)?"
67+
# You can do nesting
68+
(r"/(one|two|three)", [
69+
(r"/one", One), # $ MISSING: routeSetup="/one"
70+
(r"/two", Two), # $ MISSING: routeSetup="/two"
71+
(r"/three", Three) # $ MISSING: routeSetup="/three"
72+
]),
73+
# which is _one_ recommended way to ensure known host is used
74+
(tornado.routing.HostMatches(r"(localhost|127\.0\.0\.1)"), [
75+
("/only-localhost", OnlyLocalhost) # $ MISSING: routeSetup="/only-localhost"
76+
]),
77+
78+
],
79+
debug=True,
80+
)
81+
app.add_handlers(r".*", [(r"/added-later/([0-9]+)/([0-9]+)?", AddedLater)]) # $ MISSING: routeSetup="/added-later/([0-9]+)/([0-9]+)?"
82+
return app
83+
84+
85+
if __name__ == "__main__":
86+
87+
import tornado.ioloop
88+
app = make_app()
89+
app.listen(8888)
90+
tornado.ioloop.IOLoop.current().start()
91+
92+
# http://localhost:8888/foo/42/
93+
# http://localhost:8888/foo/42/1337
94+
95+
# http://localhost:8888/bar/42/
96+
# http://localhost:8888/bar/42/1337
97+
98+
# http://localhost:8888/baz/42/
99+
# http://localhost:8888/baz/42/1337
100+
101+
# http://localhost:8888/kw-args/42/
102+
# http://localhost:8888/kw-args/42/1337
103+
104+
# http://localhost:8888/only-localhost
105+
106+
# http://localhost:8888/one
107+
# http://localhost:8888/two
108+
# http://localhost:8888/three
109+
110+
# http://localhost:8888/added-later
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import tornado.web
2+
3+
4+
class TaintTest(tornado.web.RequestHandler):
5+
def get(self, name = "World!", number="0", foo="foo"): # $ MISSING: requestHandler routedParameter=name routedParameter=number
6+
ensure_tainted(name, number)
7+
ensure_not_tainted(foo)
8+
9+
ensure_tainted(
10+
# see https://www.tornadoweb.org/en/stable/web.html#input
11+
self.get_argument("name"),
12+
self.get_arguments("name"),
13+
self.get_arguments("name")[0],
14+
15+
self.get_body_argument("name"),
16+
self.get_body_arguments("name"),
17+
self.get_body_arguments("name")[0],
18+
19+
self.get_query_argument("name"),
20+
self.get_query_arguments("name"),
21+
self.get_query_arguments("name")[0],
22+
23+
self.path_args,
24+
self.path_args[0],
25+
26+
self.path_kwargs,
27+
self.path_kwargs["name"],
28+
)
29+
30+
request = self.request
31+
32+
ensure_tainted(
33+
# see https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest
34+
request,
35+
36+
request.uri,
37+
request.path,
38+
request.query,
39+
request.full_url(),
40+
41+
request.remote_ip,
42+
43+
request.body,
44+
45+
request.arguments,
46+
request.arguments["name"],
47+
request.arguments["name"][0],
48+
49+
request.query_arguments,
50+
request.query_arguments["name"],
51+
request.query_arguments["name"][0],
52+
53+
request.body_arguments,
54+
request.body_arguments["name"],
55+
request.body_arguments["name"][0],
56+
57+
# dict-like, see https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPHeaders
58+
request.headers,
59+
request.headers["header-name"],
60+
request.headers.get_list("header-name"),
61+
request.headers.get_all(),
62+
[(k, v) for (k, v) in request.headers.get_all()],
63+
64+
# Dict[str, http.cookies.Morsel]
65+
request.cookies,
66+
request.cookies["cookie-name"],
67+
request.cookies["cookie-name"].key,
68+
request.cookies["cookie-name"].value,
69+
)
70+
71+
72+
def make_app():
73+
return tornado.web.Application(
74+
[
75+
(r"/test_taint/([^/]+)/([0-9]+)", TaintTest), # $ MISSING: routeSetup="/test_taint/([^/]+)/([0-9]+)"
76+
],
77+
debug=True,
78+
)
79+
80+
81+
if __name__ == "__main__":
82+
import tornado.ioloop
83+
84+
app = make_app()
85+
app.listen(8888)
86+
tornado.ioloop.IOLoop.current().start()
87+
88+
# http://localhost:8888/ResponseWriting/str
89+
# http://localhost:8888/ResponseWriting/bytes
90+
# http://localhost:8888/ResponseWriting/dict

0 commit comments

Comments
 (0)