mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
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:
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user