Skip to content

Commit 7ba0939

Browse files
authored
Merge pull request #4995 from RasmusWL/tornado-model-http-sinks
Python: model HTTP sink in Tornado
2 parents 91caa13 + b55817a commit 7ba0939

File tree

6 files changed

+146
-60
lines changed

6 files changed

+146
-60
lines changed

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,17 @@ private module Tornado {
216216
/** Gets a reference to one of the methods `get_arguments`, `get_body_arguments`, `get_query_arguments`. */
217217
DataFlow::Node argumentsMethod() { result = argumentsMethod(DataFlow::TypeTracker::end()) }
218218

219+
/** Gets a reference to the `write` method. */
220+
private DataFlow::Node writeMethod(DataFlow::TypeTracker t) {
221+
t.startInAttr("write") and
222+
result = instance()
223+
or
224+
exists(DataFlow::TypeTracker t2 | result = writeMethod(t2).track(t2, t))
225+
}
226+
227+
/** Gets a reference to the `write` method. */
228+
DataFlow::Node writeMethod() { result = writeMethod(DataFlow::TypeTracker::end()) }
229+
219230
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
220231
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
221232
// Method access
@@ -540,4 +551,29 @@ private module Tornado {
540551
not result = this.getArg(0)
541552
}
542553
}
554+
555+
// ---------------------------------------------------------------------------
556+
// Response modeling
557+
// ---------------------------------------------------------------------------
558+
/**
559+
* A call to `tornado.web.RequestHandler.write` method.
560+
*
561+
* See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.write
562+
*/
563+
private class TornadoRequestHandlerWriteCall extends HTTP::Server::HttpResponse::Range,
564+
DataFlow::CfgNode {
565+
override CallNode node;
566+
567+
TornadoRequestHandlerWriteCall() {
568+
node.getFunction() = tornado::web::RequestHandler::writeMethod().asCfgNode()
569+
}
570+
571+
override DataFlow::Node getBody() {
572+
result.asCfgNode() in [node.getArg(0), node.getArgByName("chunk")]
573+
}
574+
575+
override string getMimetypeDefault() { result = "text/html" }
576+
577+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
578+
}
543579
}
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import python
22
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-
// }
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+
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@
33

44
class BasicHandler(tornado.web.RequestHandler):
55
def get(self): # $ requestHandler
6-
self.write("BasicHandler " + self.get_argument("xss"))
6+
self.write("BasicHandler " + self.get_argument("xss")) # $ HttpResponse
77

88
def post(self): # $ requestHandler
9-
self.write("BasicHandler (POST)")
9+
self.write("BasicHandler (POST)") # $ HttpResponse
1010

1111

1212
class DeepInheritance(BasicHandler):
1313
def get(self): # $ requestHandler
14-
self.write("DeepInheritance" + self.get_argument("also_xss"))
14+
self.write("DeepInheritance" + self.get_argument("also_xss")) # $ HttpResponse
1515

1616

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

2222

2323
class RedirectHandler(tornado.web.RequestHandler):
@@ -30,7 +30,7 @@ def get(self): # $ requestHandler
3030

3131
class BaseReverseInheritance(tornado.web.RequestHandler):
3232
def get(self): # $ requestHandler
33-
self.write("hello from BaseReverseInheritance")
33+
self.write("hello from BaseReverseInheritance") # $ HttpResponse
3434

3535

3636
class ReverseInheritance(BaseReverseInheritance):
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import tornado.web
2+
import tornado.httputil
3+
4+
5+
class ResponseWriting(tornado.web.RequestHandler):
6+
def get(self, type_): # $ requestHandler routedParameter=type_
7+
if type_ == "str":
8+
self.write("foo") # $ HttpResponse mimetype=text/html responseBody="foo"
9+
elif type_ == "bytes":
10+
self.write(b"foo") # $ HttpResponse mimetype=text/html responseBody=b"foo"
11+
elif type_ == "dict":
12+
d = {"foo": 42}
13+
# Content-type will be set to `application/json`
14+
self.write(d) # $ HttpResponse responseBody=d MISSING: mimetype=application/json SPURIOUS: mimetype=text/html
15+
else:
16+
raise Exception("Bad type {} {}".format(type_, type(type_)))
17+
18+
19+
class ExplicitContentType(tornado.web.RequestHandler):
20+
def get(self): # $ requestHandler
21+
# Note: Our current modeling makes it quite hard to give a good annotation here
22+
# this write is technically while the HTTP response has mimetype text/html, but
23+
# the returned HTTP response will have mimetype text/plain, which is _really_
24+
# what matters.
25+
26+
self.write("foo") # $ HttpResponse mimetype=text/html responseBody="foo"
27+
self.set_header("Content-Type", "text/plain; charset=utf-8")
28+
29+
def post(self): # $ requestHandler
30+
self.set_header("Content-Type", "text/plain; charset=utf-8")
31+
self.write("foo") # $ HttpResponse responseBody="foo" MISSING: mimetype=text/plain SPURIOUS: mimetype=text/html
32+
33+
34+
class ExampleRedirect(tornado.web.RequestHandler):
35+
def get(self): # $ requestHandler
36+
self.redirect("http://example.com") # TODO: Model redirect
37+
38+
39+
class ExampleConnectionWrite(tornado.web.RequestHandler):
40+
def get(self, stream=False): # $ requestHandler routedParameter=stream
41+
42+
if not stream:
43+
self.request.connection.write_headers(
44+
tornado.httputil.ResponseStartLine('', 200, 'OK'),
45+
tornado.httputil.HTTPHeaders(),
46+
)
47+
self.request.connection.write(b"foo") # $ MISSING: HttpResponse responseBody=b"foo"
48+
self.request.connection.finish()
49+
else:
50+
# Note: The documentation says that connection.detach(): "May only be called
51+
# during HTTPMessageDelegate.headers_received". Doing it here actually
52+
# works, but does make tornado spit out some errors... good enough to
53+
# illustrate the behavior.
54+
#
55+
# https://www.tornadoweb.org/en/stable/http1connection.html#tornado.http1connection.HTTP1Connection.detach
56+
stream = self.request.connection.detach()
57+
stream.write(b"foo stream") # $ MISSING: HttpResponse responseBody=b"foo stream"
58+
stream.close()
59+
60+
def make_app():
61+
return tornado.web.Application(
62+
[
63+
(r"/ResponseWriting/(str|bytes|dict)", ResponseWriting), # $ routeSetup="/ResponseWriting/(str|bytes|dict)"
64+
(r"/ExplicitContentType", ExplicitContentType), # $ routeSetup="/ExplicitContentType"
65+
(r"/ExampleRedirect", ExampleRedirect), # $ routeSetup="/ExampleRedirect"
66+
(r"/ExampleConnectionWrite", ExampleConnectionWrite), # $ routeSetup="/ExampleConnectionWrite"
67+
(r"/ExampleConnectionWrite/(stream)", ExampleConnectionWrite), # $ routeSetup="/ExampleConnectionWrite/(stream)"
68+
],
69+
debug=True,
70+
)
71+
72+
73+
if __name__ == "__main__":
74+
import tornado.ioloop
75+
76+
app = make_app()
77+
app.listen(8888)
78+
tornado.ioloop.IOLoop.current().start()
79+
80+
# http://localhost:8888/ResponseWriting/str
81+
# http://localhost:8888/ResponseWriting/bytes
82+
# http://localhost:8888/ResponseWriting/dict
83+
# http://localhost:8888/ExplicitContentType
84+
# http://localhost:8888/ExampleRedirect
85+
# http://localhost:8888/ExampleConnectionWrite

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

Lines changed: 0 additions & 35 deletions
This file was deleted.

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,55 @@
44

55
class FooHandler(tornado.web.RequestHandler):
66
def get(self, x, y=None, not_used=None): # $ requestHandler routedParameter=x routedParameter=y
7-
self.write("FooHandler {} {}".format(x, y))
7+
self.write("FooHandler {} {}".format(x, y)) # $ HttpResponse
88

99

1010
class BarHandler(tornado.web.RequestHandler):
1111
def get(self, x, y=None, not_used=None): # $ requestHandler routedParameter=x routedParameter=y SPURIOUS: routedParameter=not_used
12-
self.write("BarHandler {} {}".format(x, y))
12+
self.write("BarHandler {} {}".format(x, y)) # $ HttpResponse
1313

1414

1515
class BazHandler(tornado.web.RequestHandler):
1616
def get(self, x, y=None, not_used=None): # $ requestHandler routedParameter=x routedParameter=y SPURIOUS: routedParameter=not_used
17-
self.write("BazHandler {} {}".format(x, y))
17+
self.write("BazHandler {} {}".format(x, y)) # $ HttpResponse
1818

1919

2020
class KwArgs(tornado.web.RequestHandler):
2121
def get(self, *, x, y=None, not_used=None): # $ requestHandler routedParameter=x routedParameter=y
22-
self.write("KwArgs {} {}".format(x, y))
22+
self.write("KwArgs {} {}".format(x, y)) # $ HttpResponse
2323

2424

2525
class OnlyLocalhost(tornado.web.RequestHandler):
2626
def get(self): # $ requestHandler
27-
self.write("OnlyLocalhost")
27+
self.write("OnlyLocalhost") # $ HttpResponse
2828

2929

3030
class One(tornado.web.RequestHandler):
3131
def get(self): # $ requestHandler
32-
self.write("One")
32+
self.write("One") # $ HttpResponse
3333

3434

3535
class Two(tornado.web.RequestHandler):
3636
def get(self): # $ requestHandler
37-
self.write("Two")
37+
self.write("Two") # $ HttpResponse
3838

3939

4040
class Three(tornado.web.RequestHandler):
4141
def get(self): # $ requestHandler
42-
self.write("Three")
42+
self.write("Three") # $ HttpResponse
4343

4444

4545
class AddedLater(tornado.web.RequestHandler):
4646
def get(self, x, y=None, not_used=None): # $ requestHandler routedParameter=x routedParameter=y
47-
self.write("AddedLater {} {}".format(x, y))
47+
self.write("AddedLater {} {}".format(x, y)) # $ HttpResponse
4848

4949

5050
class PossiblyNotRouted(tornado.web.RequestHandler):
5151
# Even if our analysis can't find a route-setup for this class, we should still
5252
# consider it to be a handle incoming HTTP requests
5353

5454
def get(self): # $ requestHandler
55-
self.write("NotRouted")
55+
self.write("NotRouted") # $ HttpResponse
5656

5757

5858
def make_app():

0 commit comments

Comments
 (0)