mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
Python: Add modeling of http.server.BaseHTTPRequestHandler
This commit is contained in:
@@ -7,3 +7,4 @@ import semmle.python.web.bottle.Request
|
||||
import semmle.python.web.turbogears.Request
|
||||
import semmle.python.web.falcon.Request
|
||||
import semmle.python.web.cherrypy.Request
|
||||
import semmle.python.web.stdlib.Request
|
||||
|
||||
54
python/ql/src/semmle/python/web/stdlib/Request.qll
Normal file
54
python/ql/src/semmle/python/web/stdlib/Request.qll
Normal file
@@ -0,0 +1,54 @@
|
||||
import python
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.web.Http
|
||||
|
||||
class StdLibRequestSource extends HttpRequestTaintSource {
|
||||
StdLibRequestSource() {
|
||||
exists(ClassValue cls |
|
||||
cls.getABaseType+() = Value::named("BaseHTTPServer.BaseHTTPRequestHandler")
|
||||
or
|
||||
cls.getABaseType+() = Value::named("http.server.BaseHTTPRequestHandler")
|
||||
|
|
||||
this.(ControlFlowNode).pointsTo().getClass() = cls
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) { kind instanceof BaseHTTPRequestHandlerKind }
|
||||
}
|
||||
|
||||
class BaseHTTPRequestHandlerKind extends TaintKind {
|
||||
BaseHTTPRequestHandlerKind() { this = "BaseHTTPRequestHandlerKind" }
|
||||
|
||||
override TaintKind getTaintOfAttribute(string name) {
|
||||
name in ["requestline", "path"] and
|
||||
result instanceof ExternalStringKind
|
||||
or
|
||||
name = "headers" and
|
||||
result instanceof HTTPMessageKind
|
||||
or
|
||||
name = "rfile" and
|
||||
result instanceof ExternalFileObject
|
||||
}
|
||||
}
|
||||
|
||||
class HTTPMessageKind extends ExternalStringDictKind {
|
||||
override TaintKind getTaintOfMethodResult(string name) {
|
||||
result = super.getTaintOfMethodResult(name)
|
||||
or
|
||||
name = "get_all" and
|
||||
result.(SequenceKind).getItem() = this.getValue()
|
||||
or
|
||||
name in ["as_bytes", "as_string"] and
|
||||
result instanceof ExternalStringKind
|
||||
}
|
||||
|
||||
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
|
||||
result = super.getTaintForFlowStep(fromnode, tonode)
|
||||
or
|
||||
exists(ClassValue cls | cls = ClassValue::unicode() or cls = ClassValue::bytes() |
|
||||
tonode = cls.getACall() and
|
||||
tonode.(CallNode).getArg(0) = fromnode and
|
||||
result instanceof ExternalStringKind
|
||||
)
|
||||
}
|
||||
}
|
||||
16
python/ql/test/library-tests/web/stdlib/TestTaint.expected
Normal file
16
python/ql/test/library-tests/web/stdlib/TestTaint.expected
Normal file
@@ -0,0 +1,16 @@
|
||||
| test.py:16 | ok | taint_sources | self | BaseHTTPRequestHandlerKind |
|
||||
| test.py:18 | ok | taint_sources | Attribute | externally controlled string |
|
||||
| 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: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 |
|
||||
32
python/ql/test/library-tests/web/stdlib/TestTaint.ql
Normal file
32
python/ql/test/library-tests/web/stdlib/TestTaint.ql
Normal file
@@ -0,0 +1,32 @@
|
||||
import python
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
from
|
||||
Call call, Expr arg, boolean expected_taint, boolean has_taint, string test_res,
|
||||
string taint_string
|
||||
where
|
||||
call.getLocation().getFile().getShortName() = "test.py" and
|
||||
(
|
||||
call.getFunc().(Name).getId() = "ensure_tainted" and
|
||||
expected_taint = true
|
||||
or
|
||||
call.getFunc().(Name).getId() = "ensure_not_tainted" and
|
||||
expected_taint = false
|
||||
) and
|
||||
arg = call.getAnArg() and
|
||||
(
|
||||
not exists(TaintedNode tainted | tainted.getAstNode() = arg) and
|
||||
taint_string = "<NO TAINT>" and
|
||||
has_taint = false
|
||||
or
|
||||
exists(TaintedNode tainted | tainted.getAstNode() = arg |
|
||||
taint_string = tainted.getTaintKind().toString()
|
||||
) and
|
||||
has_taint = true
|
||||
) and
|
||||
if expected_taint = has_taint then test_res = "ok " else test_res = "fail"
|
||||
// if expected_taint = has_taint then test_res = "✓" else test_res = "✕"
|
||||
select arg.getLocation().toString(), test_res, call.getScope().(Function).getName(), arg.toString(),
|
||||
taint_string
|
||||
54
python/ql/test/library-tests/web/stdlib/test.py
Normal file
54
python/ql/test/library-tests/web/stdlib/test.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
from BaseHTTPServer import HTTPServer
|
||||
|
||||
if sys.version_info[0] == 3:
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
|
||||
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(),
|
||||
)
|
||||
|
||||
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")
|
||||
print(self.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"
|
||||
Reference in New Issue
Block a user