mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Python: Model cgi.FieldStorage (parsing of submitted forms)
This commit is contained in:
@@ -55,3 +55,67 @@ class HTTPMessageKind extends ExternalStringDictKind {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Source of parsed HTTP forms (by using the `cgi` module). */
|
||||
class CgiFieldStorageSource extends HttpRequestTaintSource {
|
||||
CgiFieldStorageSource() { this = Value::named("cgi.FieldStorage").getACall() }
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) { kind instanceof CgiFieldStorageFormKind }
|
||||
}
|
||||
|
||||
/** TaintKind for a parsed HTTP form. */
|
||||
class CgiFieldStorageFormKind extends TaintKind {
|
||||
/*
|
||||
* There is a slight difference between how we model form/fields and how it is handled by the code.
|
||||
* In the code
|
||||
* ```
|
||||
* form = cgi.FieldStorage()
|
||||
* field = form['myfield']
|
||||
* ```
|
||||
* both `form` and `field` have the type `cgi.FieldStorage`. This allows the code to represent
|
||||
* nested forms as `form['nested_form']['myfield']`. However, since HTML forms can't be nested
|
||||
* we ignore that detail since it allows for a more clean modeling.
|
||||
*/
|
||||
CgiFieldStorageFormKind() { this = "CgiFieldStorageFormKind" }
|
||||
|
||||
override TaintKind getTaintOfAttribute(string name) {
|
||||
name = "value" and result.(SequenceKind).getItem() instanceof CgiFieldStorageFieldKind
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfMethodResult(string name) {
|
||||
name = "getvalue" and
|
||||
(
|
||||
result instanceof ExternalStringKind
|
||||
or
|
||||
result.(SequenceKind).getItem() instanceof ExternalStringKind
|
||||
)
|
||||
or
|
||||
name = "getfirst" and
|
||||
result instanceof ExternalStringKind
|
||||
or
|
||||
name = "getlist" and
|
||||
result.(SequenceKind).getItem() instanceof ExternalStringKind
|
||||
}
|
||||
|
||||
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
|
||||
tonode.(SubscriptNode).getObject() = fromnode and
|
||||
(
|
||||
result instanceof CgiFieldStorageFieldKind
|
||||
or
|
||||
result.(SequenceKind).getItem() instanceof CgiFieldStorageFieldKind
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** TaintKind for the field of a parsed HTTP form. */
|
||||
class CgiFieldStorageFieldKind extends TaintKind {
|
||||
CgiFieldStorageFieldKind() { this = "CgiFieldStorageFieldKind" }
|
||||
|
||||
override TaintKind getTaintOfAttribute(string name) {
|
||||
name = "filename" and result instanceof ExternalStringKind
|
||||
or
|
||||
name = "file" and result instanceof ExternalFileObject
|
||||
or
|
||||
name = "value" and result instanceof ExternalStringKind
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
| test.py:16 | ok | taint_sources | self | BaseHTTPRequestHandlerKind |
|
||||
| test.py:18 | ok | taint_sources | Attribute | externally controlled string |
|
||||
| test.py:18 | ok | taint_sources | self | BaseHTTPRequestHandlerKind |
|
||||
| test.py:20 | ok | taint_sources | Attribute | externally controlled string |
|
||||
| test.py:22 | ok | taint_sources | Attribute | {externally controlled string} |
|
||||
| test.py:23 | ok | taint_sources | Subscript | externally controlled string |
|
||||
| test.py:24 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:25 | ok | taint_sources | Attribute() | [externally controlled string] |
|
||||
| test.py:26 | fail | taint_sources | Attribute() | <NO TAINT> |
|
||||
| test.py:22 | ok | taint_sources | Attribute | externally controlled string |
|
||||
| test.py:24 | ok | taint_sources | Attribute | {externally controlled string} |
|
||||
| test.py:25 | ok | taint_sources | Subscript | externally controlled string |
|
||||
| test.py:26 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:27 | ok | taint_sources | Attribute() | [externally controlled string] |
|
||||
| test.py:28 | fail | taint_sources | Attribute() | <NO TAINT> |
|
||||
| test.py:29 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:30 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:31 | ok | taint_sources | str() | externally controlled string |
|
||||
| test.py:32 | ok | taint_sources | bytes() | externally controlled string |
|
||||
| test.py:34 | ok | taint_sources | Attribute | file[externally controlled string] |
|
||||
| test.py:35 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:29 | ok | taint_sources | Attribute() | [externally controlled string] |
|
||||
| test.py:30 | fail | taint_sources | Attribute() | <NO TAINT> |
|
||||
| test.py:31 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:32 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:33 | ok | taint_sources | str() | externally controlled string |
|
||||
| test.py:34 | ok | taint_sources | bytes() | externally controlled string |
|
||||
| test.py:36 | ok | taint_sources | Attribute | file[externally controlled string] |
|
||||
| test.py:37 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:47 | ok | taint_sources | form | CgiFieldStorageFormKind |
|
||||
| test.py:49 | ok | taint_sources | Subscript | CgiFieldStorageFieldKind |
|
||||
| test.py:49 | ok | taint_sources | Subscript | [CgiFieldStorageFieldKind] |
|
||||
| test.py:50 | ok | taint_sources | Attribute | externally controlled string |
|
||||
| test.py:51 | ok | taint_sources | Attribute | file[externally controlled string] |
|
||||
| test.py:52 | ok | taint_sources | Attribute | externally controlled string |
|
||||
| test.py:53 | ok | taint_sources | Subscript | CgiFieldStorageFieldKind |
|
||||
| test.py:54 | ok | taint_sources | Attribute | externally controlled string |
|
||||
| test.py:55 | ok | taint_sources | Attribute | file[externally controlled string] |
|
||||
| test.py:56 | ok | taint_sources | Attribute | externally controlled string |
|
||||
| test.py:58 | ok | taint_sources | Attribute() | [externally controlled string] |
|
||||
| test.py:58 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:59 | ok | taint_sources | Subscript | externally controlled string |
|
||||
| test.py:61 | ok | taint_sources | Attribute() | externally controlled string |
|
||||
| test.py:63 | ok | taint_sources | Attribute() | [externally controlled string] |
|
||||
| test.py:64 | ok | taint_sources | Subscript | externally controlled string |
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
import os
|
||||
import cgi
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
@@ -35,6 +37,33 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||
self.rfile.read(),
|
||||
)
|
||||
|
||||
form = cgi.FieldStorage(
|
||||
self.rfile,
|
||||
self.headers,
|
||||
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
|
||||
)
|
||||
|
||||
ensure_tainted(
|
||||
form,
|
||||
|
||||
form['key'],
|
||||
form['key'].value,
|
||||
form['key'].file,
|
||||
form['key'].filename,
|
||||
form['key'][0], # will be a list, if multiple fields named "key" are provided
|
||||
form['key'][0].value,
|
||||
form['key'][0].file,
|
||||
form['key'][0].filename,
|
||||
|
||||
form.getvalue('key'),
|
||||
form.getvalue('key')[0], # will be a list, if multiple fields named "key" are provided
|
||||
|
||||
form.getfirst('key'),
|
||||
|
||||
form.getlist('key'),
|
||||
form.getlist('key')[0],
|
||||
)
|
||||
|
||||
def do_GET(self):
|
||||
# send_response will log a line to stderr
|
||||
self.send_response(200)
|
||||
@@ -44,6 +73,27 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||
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__":
|
||||
@@ -52,3 +102,6 @@ if __name__ == "__main__":
|
||||
|
||||
# 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