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
This commit is contained in:
Rasmus Wriedt Larsen
2020-11-24 18:05:12 +01:00
parent bc340e210b
commit 43688715f5
3 changed files with 162 additions and 0 deletions

View File

@@ -2,3 +2,36 @@
| CodeExecution.py:36 | ok | test_additional_taint | cmd1 |
| CodeExecution.py:37 | ok | test_additional_taint | cmd2 |
| CodeExecution.py:38 | ok | test_additional_taint | cmd3 |
| http_server.py:22 | fail | test_cgi_FieldStorage_taint | form |
| http_server.py:24 | fail | test_cgi_FieldStorage_taint | form['key'] |
| http_server.py:25 | fail | test_cgi_FieldStorage_taint | form['key'].value |
| http_server.py:26 | fail | test_cgi_FieldStorage_taint | form['key'].file |
| http_server.py:27 | fail | test_cgi_FieldStorage_taint | form['key'].filename |
| http_server.py:28 | fail | test_cgi_FieldStorage_taint | form['key'][0] |
| http_server.py:29 | fail | test_cgi_FieldStorage_taint | form['key'][0].value |
| http_server.py:30 | fail | test_cgi_FieldStorage_taint | form['key'][0].file |
| http_server.py:31 | fail | test_cgi_FieldStorage_taint | form['key'][0].filename |
| http_server.py:32 | fail | test_cgi_FieldStorage_taint | ListComp |
| http_server.py:34 | fail | test_cgi_FieldStorage_taint | form.getvalue(..) |
| http_server.py:35 | fail | test_cgi_FieldStorage_taint | form.getvalue(..)[0] |
| http_server.py:37 | fail | test_cgi_FieldStorage_taint | form.getfirst(..) |
| http_server.py:39 | fail | test_cgi_FieldStorage_taint | form.getlist(..) |
| http_server.py:40 | fail | test_cgi_FieldStorage_taint | form.getlist(..)[0] |
| http_server.py:41 | fail | test_cgi_FieldStorage_taint | ListComp |
| http_server.py:50 | fail | taint_sources | self |
| http_server.py:52 | fail | taint_sources | self.requestline |
| http_server.py:54 | fail | taint_sources | self.path |
| http_server.py:56 | fail | taint_sources | self.headers |
| http_server.py:57 | fail | taint_sources | self.headers['Foo'] |
| http_server.py:58 | fail | taint_sources | self.headers.get(..) |
| http_server.py:59 | fail | taint_sources | self.headers.get_all(..) |
| http_server.py:60 | fail | taint_sources | self.headers.keys() |
| http_server.py:61 | fail | taint_sources | self.headers.values() |
| http_server.py:62 | fail | taint_sources | self.headers.items() |
| http_server.py:63 | fail | taint_sources | self.headers.as_bytes() |
| http_server.py:64 | fail | taint_sources | self.headers.as_string() |
| http_server.py:65 | fail | taint_sources | str(..) |
| http_server.py:66 | fail | taint_sources | bytes(..) |
| http_server.py:68 | fail | taint_sources | self.rfile |
| http_server.py:69 | fail | taint_sources | self.rfile.read() |
| http_server.py:78 | fail | taint_sources | form |

View File

@@ -1,2 +1,9 @@
import experimental.dataflow.tainttracking.TestTaintLib
import semmle.python.dataflow.new.RemoteFlowSources
class WithRemoteFlowSources extends TestTaintTrackingConfiguration {
override predicate isSource(DataFlow::Node source) {
super.isSource(source) or
source instanceof RemoteFlowSource
}
}

View File

@@ -0,0 +1,122 @@
import sys
import os
import cgi
if sys.version_info[0] == 2:
from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
from CGIHTTPServer import CGIHTTPRequestHandler
if sys.version_info[0] == 3:
from http.server import HTTPServer, BaseHTTPRequestHandler, SimpleHTTPRequestHandler, CGIHTTPRequestHandler
def test_cgi_FieldStorage_taint():
# When a python script is invoked through CGI, the default values used by
# `cgi.FieldStorage` constructor makes it handle data from incoming request.
# You _can_ also manually set the input-data, as is shown below in `MyHandler`.
form = cgi.FieldStorage()
ensure_tainted(
form,
form['key'], # will be a list, if multiple fields named "key" are provided
form['key'].value,
form['key'].file,
form['key'].filename,
form['key'][0],
form['key'][0].value,
form['key'][0].file,
form['key'][0].filename,
[field.value for field in form['key']],
form.getvalue('key'), # will be a list, if multiple fields named "key" are provided
form.getvalue('key')[0],
form.getfirst('key'),
form.getlist('key'),
form.getlist('key')[0],
[field.value for field in form.getlist('key')],
)
class MyHandler(BaseHTTPRequestHandler):
def taint_sources(self):
ensure_tainted(
self,
self.requestline,
self.path,
self.headers,
self.headers['Foo'],
self.headers.get('Foo'),
self.headers.get_all('Foo'),
self.headers.keys(),
self.headers.values(),
self.headers.items(),
self.headers.as_bytes(),
self.headers.as_string(),
str(self.headers),
bytes(self.headers),
self.rfile,
self.rfile.read(),
)
form = cgi.FieldStorage(
self.rfile,
self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
)
ensure_tainted(form)
def do_GET(self):
# send_response will log a line to stderr
self.send_response(200)
self.send_header("Content-type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(b"Hello BaseHTTPRequestHandler\n")
self.wfile.writelines([b"1\n", b"2\n", b"3\n"])
print(self.headers)
def do_POST(self):
form = cgi.FieldStorage(
self.rfile,
self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
)
if 'myfile' not in form:
self.send_response(422)
self.end_headers()
return
field = form['myfile']
field.file.seek(0, os.SEEK_END)
filesize = field.file.tell()
print("Uploaded {!r} with {} bytes".format(field.filename, filesize))
self.send_response(200)
self.end_headers()
if __name__ == "__main__":
server = HTTPServer(("127.0.0.1", 8080), MyHandler)
server.serve_forever()
# Headers works case insensitvely, so self.headers['foo'] == self.headers['FOO']
# curl localhost:8080 --header "Foo: 1" --header "foo: 2"
# To test file submission through forms, use
# curl -F myfile=@<yourfile> localhost:8080