Skip to content

Commit de8ac6c

Browse files
authored
Merge pull request github#4869 from RasmusWL/tornado-source-modeling
Python: Add Tornado source modeling
2 parents c69b776 + 14bb10a commit de8ac6c

File tree

13 files changed

+913
-0
lines changed

13 files changed

+913
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Added modeling of sources of remote user input (`RemoteFlowSource`) when using `tornado` to create HTTP servers, to the new data-flow queries.

python/ql/src/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ private import semmle.python.frameworks.MySQLdb
1212
private import semmle.python.frameworks.Psycopg2
1313
private import semmle.python.frameworks.PyMySQL
1414
private import semmle.python.frameworks.Stdlib
15+
private import semmle.python.frameworks.Tornado
1516
private import semmle.python.frameworks.Yaml

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

Lines changed: 543 additions & 0 deletions
Large diffs are not rendered by default.

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 | ok | get | name |
2+
| taint_test.py:6 | ok | get | number |
3+
| taint_test.py:7 | ok | get | foo |
4+
| taint_test.py:11 | ok | get | self.get_argument(..) |
5+
| taint_test.py:12 | ok | get | self.get_arguments(..) |
6+
| taint_test.py:13 | ok | get | self.get_arguments(..)[0] |
7+
| taint_test.py:15 | ok | get | self.get_body_argument(..) |
8+
| taint_test.py:16 | ok | get | self.get_body_arguments(..) |
9+
| taint_test.py:17 | ok | get | self.get_body_arguments(..)[0] |
10+
| taint_test.py:19 | ok | get | self.get_query_argument(..) |
11+
| taint_test.py:20 | ok | get | self.get_query_arguments(..) |
12+
| taint_test.py:21 | ok | get | self.get_query_arguments(..)[0] |
13+
| taint_test.py:23 | ok | get | self.path_args |
14+
| taint_test.py:24 | ok | get | self.path_args[0] |
15+
| taint_test.py:26 | ok | get | self.path_kwargs |
16+
| taint_test.py:27 | ok | get | self.path_kwargs["name"] |
17+
| taint_test.py:34 | ok | get | request |
18+
| taint_test.py:36 | ok | get | request.uri |
19+
| taint_test.py:37 | ok | get | request.path |
20+
| taint_test.py:38 | ok | get | request.query |
21+
| taint_test.py:39 | ok | get | request.full_url() |
22+
| taint_test.py:41 | ok | get | request.remote_ip |
23+
| taint_test.py:43 | ok | get | request.body |
24+
| taint_test.py:45 | ok | get | request.arguments |
25+
| taint_test.py:46 | ok | get | request.arguments["name"] |
26+
| taint_test.py:47 | ok | get | request.arguments["name"][0] |
27+
| taint_test.py:49 | ok | get | request.query_arguments |
28+
| taint_test.py:50 | ok | get | request.query_arguments["name"] |
29+
| taint_test.py:51 | ok | get | request.query_arguments["name"][0] |
30+
| taint_test.py:53 | ok | get | request.body_arguments |
31+
| taint_test.py:54 | ok | get | request.body_arguments["name"] |
32+
| taint_test.py:55 | ok | get | request.body_arguments["name"][0] |
33+
| taint_test.py:58 | ok | get | request.headers |
34+
| taint_test.py:59 | ok | 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 | ok | get | request.cookies |
39+
| taint_test.py:66 | ok | 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+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import tornado.web
2+
3+
4+
class BasicHandler(tornado.web.RequestHandler):
5+
def get(self): # $ requestHandler
6+
self.write("BasicHandler " + self.get_argument("xss"))
7+
8+
def post(self): # $ requestHandler
9+
self.write("BasicHandler (POST)")
10+
11+
12+
class DeepInheritance(BasicHandler):
13+
def get(self): # $ requestHandler
14+
self.write("DeepInheritance" + self.get_argument("also_xss"))
15+
16+
17+
class FormHandler(tornado.web.RequestHandler):
18+
def post(self): # $ requestHandler
19+
name = self.get_body_argument("name")
20+
self.write(name)
21+
22+
23+
class RedirectHandler(tornado.web.RequestHandler):
24+
def get(self): # $ requestHandler
25+
req = self.request
26+
h = req.headers
27+
url = h["url"]
28+
self.redirect(url)
29+
30+
31+
class BaseReverseInheritance(tornado.web.RequestHandler):
32+
def get(self): # $ requestHandler
33+
self.write("hello from BaseReverseInheritance")
34+
35+
36+
class ReverseInheritance(BaseReverseInheritance):
37+
pass
38+
39+
40+
def make_app():
41+
return tornado.web.Application(
42+
[
43+
(r"/basic", BasicHandler), # $ routeSetup="/basic"
44+
(r"/deep", DeepInheritance), # $ routeSetup="/deep"
45+
(r"/form", FormHandler), # $ routeSetup="/form"
46+
(r"/redirect", RedirectHandler), # $ routeSetup="/redirect"
47+
(r"/reverse-inheritance", ReverseInheritance), # $ routeSetup="/reverse-inheritance"
48+
],
49+
debug=True,
50+
)
51+
52+
53+
if __name__ == "__main__":
54+
import tornado.ioloop
55+
56+
app = make_app()
57+
app.listen(8888)
58+
tornado.ioloop.IOLoop.current().start()
59+
60+
# http://localhost:8888/basic?xss=foo
61+
# http://localhost:8888/deep?also_xss=foo
62+
63+
# curl -X POST http://localhost:8888/basic
64+
# curl -X POST http://localhost:8888/deep
65+
66+
# curl -X POST -F "name=foo" http://localhost:8888/form
67+
# curl -v -H 'url: http://example.com' http://localhost:8888/redirect
68+
69+
# http://localhost:8888/reverse-inheritance
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

0 commit comments

Comments
 (0)