Skip to content

Commit 26ed911

Browse files
committed
Python: Add modeling of http.server.BaseHTTPRequestHandler
1 parent 243dea7 commit 26ed911

File tree

5 files changed

+157
-0
lines changed

5 files changed

+157
-0
lines changed

python/ql/src/semmle/python/web/HttpRequest.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ import semmle.python.web.bottle.Request
77
import semmle.python.web.turbogears.Request
88
import semmle.python.web.falcon.Request
99
import semmle.python.web.cherrypy.Request
10+
import semmle.python.web.stdlib.Request
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import python
2+
import semmle.python.security.TaintTracking
3+
import semmle.python.web.Http
4+
5+
class StdLibRequestSource extends HttpRequestTaintSource {
6+
StdLibRequestSource() {
7+
exists(ClassValue cls |
8+
cls.getABaseType+() = Value::named("BaseHTTPServer.BaseHTTPRequestHandler")
9+
or
10+
cls.getABaseType+() = Value::named("http.server.BaseHTTPRequestHandler")
11+
|
12+
this.(ControlFlowNode).pointsTo().getClass() = cls
13+
)
14+
}
15+
16+
override predicate isSourceOf(TaintKind kind) { kind instanceof BaseHTTPRequestHandlerKind }
17+
}
18+
19+
class BaseHTTPRequestHandlerKind extends TaintKind {
20+
BaseHTTPRequestHandlerKind() { this = "BaseHTTPRequestHandlerKind" }
21+
22+
override TaintKind getTaintOfAttribute(string name) {
23+
name in ["requestline", "path"] and
24+
result instanceof ExternalStringKind
25+
or
26+
name = "headers" and
27+
result instanceof HTTPMessageKind
28+
or
29+
name = "rfile" and
30+
result instanceof ExternalFileObject
31+
}
32+
}
33+
34+
class HTTPMessageKind extends ExternalStringDictKind {
35+
override TaintKind getTaintOfMethodResult(string name) {
36+
result = super.getTaintOfMethodResult(name)
37+
or
38+
name = "get_all" and
39+
result.(SequenceKind).getItem() = this.getValue()
40+
or
41+
name in ["as_bytes", "as_string"] and
42+
result instanceof ExternalStringKind
43+
}
44+
45+
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
46+
result = super.getTaintForFlowStep(fromnode, tonode)
47+
or
48+
exists(ClassValue cls | cls = ClassValue::unicode() or cls = ClassValue::bytes() |
49+
tonode = cls.getACall() and
50+
tonode.(CallNode).getArg(0) = fromnode and
51+
result instanceof ExternalStringKind
52+
)
53+
}
54+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
| test.py:16 | ok | taint_sources | self | BaseHTTPRequestHandlerKind |
2+
| test.py:18 | ok | taint_sources | Attribute | externally controlled string |
3+
| test.py:20 | ok | taint_sources | Attribute | externally controlled string |
4+
| test.py:22 | ok | taint_sources | Attribute | {externally controlled string} |
5+
| test.py:23 | ok | taint_sources | Subscript | externally controlled string |
6+
| test.py:24 | ok | taint_sources | Attribute() | externally controlled string |
7+
| test.py:25 | ok | taint_sources | Attribute() | [externally controlled string] |
8+
| test.py:26 | fail | taint_sources | Attribute() | <NO TAINT> |
9+
| test.py:27 | ok | taint_sources | Attribute() | [externally controlled string] |
10+
| test.py:28 | fail | taint_sources | Attribute() | <NO TAINT> |
11+
| test.py:29 | ok | taint_sources | Attribute() | externally controlled string |
12+
| test.py:30 | ok | taint_sources | Attribute() | externally controlled string |
13+
| test.py:31 | ok | taint_sources | str() | externally controlled string |
14+
| test.py:32 | ok | taint_sources | bytes() | externally controlled string |
15+
| test.py:34 | ok | taint_sources | Attribute | file[externally controlled string] |
16+
| test.py:35 | ok | taint_sources | Attribute() | externally controlled string |
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import python
2+
import semmle.python.security.TaintTracking
3+
import semmle.python.web.HttpRequest
4+
import semmle.python.security.strings.Untrusted
5+
6+
from
7+
Call call, Expr arg, boolean expected_taint, boolean has_taint, string test_res,
8+
string taint_string
9+
where
10+
call.getLocation().getFile().getShortName() = "test.py" and
11+
(
12+
call.getFunc().(Name).getId() = "ensure_tainted" and
13+
expected_taint = true
14+
or
15+
call.getFunc().(Name).getId() = "ensure_not_tainted" and
16+
expected_taint = false
17+
) and
18+
arg = call.getAnArg() and
19+
(
20+
not exists(TaintedNode tainted | tainted.getAstNode() = arg) and
21+
taint_string = "<NO TAINT>" and
22+
has_taint = false
23+
or
24+
exists(TaintedNode tainted | tainted.getAstNode() = arg |
25+
taint_string = tainted.getTaintKind().toString()
26+
) and
27+
has_taint = true
28+
) and
29+
if expected_taint = has_taint then test_res = "ok " else test_res = "fail"
30+
// if expected_taint = has_taint then test_res = "✓" else test_res = "✕"
31+
select arg.getLocation().toString(), test_res, call.getScope().(Function).getName(), arg.toString(),
32+
taint_string
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import sys
2+
3+
if sys.version_info[0] == 2:
4+
from BaseHTTPServer import BaseHTTPRequestHandler
5+
from BaseHTTPServer import HTTPServer
6+
7+
if sys.version_info[0] == 3:
8+
from http.server import HTTPServer, BaseHTTPRequestHandler
9+
10+
11+
class MyHandler(BaseHTTPRequestHandler):
12+
13+
def taint_sources(self):
14+
15+
ensure_tainted(
16+
self,
17+
18+
self.requestline,
19+
20+
self.path,
21+
22+
self.headers,
23+
self.headers['Foo'],
24+
self.headers.get('Foo'),
25+
self.headers.get_all('Foo'),
26+
self.headers.keys(),
27+
self.headers.values(),
28+
self.headers.items(),
29+
self.headers.as_bytes(),
30+
self.headers.as_string(),
31+
str(self.headers),
32+
bytes(self.headers),
33+
34+
self.rfile,
35+
self.rfile.read(),
36+
)
37+
38+
def do_GET(self):
39+
# send_response will log a line to stderr
40+
self.send_response(200)
41+
self.send_header("Content-type", "text/plain; charset=utf-8")
42+
self.end_headers()
43+
self.wfile.write(b"Hello BaseHTTPRequestHandler")
44+
print(self.headers)
45+
46+
47+
48+
49+
if __name__ == "__main__":
50+
server = HTTPServer(("127.0.0.1", 8080), MyHandler)
51+
server.serve_forever()
52+
53+
# Headers works case insensitvely, so self.headers['foo'] == self.headers['FOO']
54+
# curl localhost:8080 --header "Foo: 1" --header "foo: 2"

0 commit comments

Comments
 (0)