Skip to content

Commit 4368871

Browse files
committed
Python: Add test of stdlib HTTP server facilities
Just a port of the old tests, except for the fact that I learned `cgi.FieldStorage()` _should_ be tainted when not specifying any arguments. (and moved taint-test to own function) Also clarified how imports of all the .*HTTPRequestHandler works in Python2
1 parent bc340e2 commit 4368871

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

python/ql/test/experimental/library-tests/frameworks/stdlib/TestTaint.expected

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,36 @@
22
| CodeExecution.py:36 | ok | test_additional_taint | cmd1 |
33
| CodeExecution.py:37 | ok | test_additional_taint | cmd2 |
44
| CodeExecution.py:38 | ok | test_additional_taint | cmd3 |
5+
| http_server.py:22 | fail | test_cgi_FieldStorage_taint | form |
6+
| http_server.py:24 | fail | test_cgi_FieldStorage_taint | form['key'] |
7+
| http_server.py:25 | fail | test_cgi_FieldStorage_taint | form['key'].value |
8+
| http_server.py:26 | fail | test_cgi_FieldStorage_taint | form['key'].file |
9+
| http_server.py:27 | fail | test_cgi_FieldStorage_taint | form['key'].filename |
10+
| http_server.py:28 | fail | test_cgi_FieldStorage_taint | form['key'][0] |
11+
| http_server.py:29 | fail | test_cgi_FieldStorage_taint | form['key'][0].value |
12+
| http_server.py:30 | fail | test_cgi_FieldStorage_taint | form['key'][0].file |
13+
| http_server.py:31 | fail | test_cgi_FieldStorage_taint | form['key'][0].filename |
14+
| http_server.py:32 | fail | test_cgi_FieldStorage_taint | ListComp |
15+
| http_server.py:34 | fail | test_cgi_FieldStorage_taint | form.getvalue(..) |
16+
| http_server.py:35 | fail | test_cgi_FieldStorage_taint | form.getvalue(..)[0] |
17+
| http_server.py:37 | fail | test_cgi_FieldStorage_taint | form.getfirst(..) |
18+
| http_server.py:39 | fail | test_cgi_FieldStorage_taint | form.getlist(..) |
19+
| http_server.py:40 | fail | test_cgi_FieldStorage_taint | form.getlist(..)[0] |
20+
| http_server.py:41 | fail | test_cgi_FieldStorage_taint | ListComp |
21+
| http_server.py:50 | fail | taint_sources | self |
22+
| http_server.py:52 | fail | taint_sources | self.requestline |
23+
| http_server.py:54 | fail | taint_sources | self.path |
24+
| http_server.py:56 | fail | taint_sources | self.headers |
25+
| http_server.py:57 | fail | taint_sources | self.headers['Foo'] |
26+
| http_server.py:58 | fail | taint_sources | self.headers.get(..) |
27+
| http_server.py:59 | fail | taint_sources | self.headers.get_all(..) |
28+
| http_server.py:60 | fail | taint_sources | self.headers.keys() |
29+
| http_server.py:61 | fail | taint_sources | self.headers.values() |
30+
| http_server.py:62 | fail | taint_sources | self.headers.items() |
31+
| http_server.py:63 | fail | taint_sources | self.headers.as_bytes() |
32+
| http_server.py:64 | fail | taint_sources | self.headers.as_string() |
33+
| http_server.py:65 | fail | taint_sources | str(..) |
34+
| http_server.py:66 | fail | taint_sources | bytes(..) |
35+
| http_server.py:68 | fail | taint_sources | self.rfile |
36+
| http_server.py:69 | fail | taint_sources | self.rfile.read() |
37+
| http_server.py:78 | fail | taint_sources | form |
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
import experimental.dataflow.tainttracking.TestTaintLib
22
import semmle.python.dataflow.new.RemoteFlowSources
3+
4+
class WithRemoteFlowSources extends TestTaintTrackingConfiguration {
5+
override predicate isSource(DataFlow::Node source) {
6+
super.isSource(source) or
7+
source instanceof RemoteFlowSource
8+
}
9+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import sys
2+
import os
3+
import cgi
4+
5+
if sys.version_info[0] == 2:
6+
from BaseHTTPServer import BaseHTTPRequestHandler
7+
from BaseHTTPServer import HTTPServer
8+
from SimpleHTTPServer import SimpleHTTPRequestHandler
9+
from CGIHTTPServer import CGIHTTPRequestHandler
10+
11+
if sys.version_info[0] == 3:
12+
from http.server import HTTPServer, BaseHTTPRequestHandler, SimpleHTTPRequestHandler, CGIHTTPRequestHandler
13+
14+
15+
def test_cgi_FieldStorage_taint():
16+
# When a python script is invoked through CGI, the default values used by
17+
# `cgi.FieldStorage` constructor makes it handle data from incoming request.
18+
# You _can_ also manually set the input-data, as is shown below in `MyHandler`.
19+
form = cgi.FieldStorage()
20+
21+
ensure_tainted(
22+
form,
23+
24+
form['key'], # will be a list, if multiple fields named "key" are provided
25+
form['key'].value,
26+
form['key'].file,
27+
form['key'].filename,
28+
form['key'][0],
29+
form['key'][0].value,
30+
form['key'][0].file,
31+
form['key'][0].filename,
32+
[field.value for field in form['key']],
33+
34+
form.getvalue('key'), # will be a list, if multiple fields named "key" are provided
35+
form.getvalue('key')[0],
36+
37+
form.getfirst('key'),
38+
39+
form.getlist('key'),
40+
form.getlist('key')[0],
41+
[field.value for field in form.getlist('key')],
42+
)
43+
44+
45+
class MyHandler(BaseHTTPRequestHandler):
46+
47+
def taint_sources(self):
48+
49+
ensure_tainted(
50+
self,
51+
52+
self.requestline,
53+
54+
self.path,
55+
56+
self.headers,
57+
self.headers['Foo'],
58+
self.headers.get('Foo'),
59+
self.headers.get_all('Foo'),
60+
self.headers.keys(),
61+
self.headers.values(),
62+
self.headers.items(),
63+
self.headers.as_bytes(),
64+
self.headers.as_string(),
65+
str(self.headers),
66+
bytes(self.headers),
67+
68+
self.rfile,
69+
self.rfile.read(),
70+
)
71+
72+
form = cgi.FieldStorage(
73+
self.rfile,
74+
self.headers,
75+
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
76+
)
77+
78+
ensure_tainted(form)
79+
80+
81+
def do_GET(self):
82+
# send_response will log a line to stderr
83+
self.send_response(200)
84+
self.send_header("Content-type", "text/plain; charset=utf-8")
85+
self.end_headers()
86+
self.wfile.write(b"Hello BaseHTTPRequestHandler\n")
87+
self.wfile.writelines([b"1\n", b"2\n", b"3\n"])
88+
print(self.headers)
89+
90+
91+
def do_POST(self):
92+
form = cgi.FieldStorage(
93+
self.rfile,
94+
self.headers,
95+
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
96+
)
97+
98+
if 'myfile' not in form:
99+
self.send_response(422)
100+
self.end_headers()
101+
return
102+
103+
field = form['myfile']
104+
105+
field.file.seek(0, os.SEEK_END)
106+
filesize = field.file.tell()
107+
108+
print("Uploaded {!r} with {} bytes".format(field.filename, filesize))
109+
110+
self.send_response(200)
111+
self.end_headers()
112+
113+
114+
if __name__ == "__main__":
115+
server = HTTPServer(("127.0.0.1", 8080), MyHandler)
116+
server.serve_forever()
117+
118+
# Headers works case insensitvely, so self.headers['foo'] == self.headers['FOO']
119+
# curl localhost:8080 --header "Foo: 1" --header "foo: 2"
120+
121+
# To test file submission through forms, use
122+
# curl -F myfile=@<yourfile> localhost:8080

0 commit comments

Comments
 (0)