Skip to content

Commit 1b25573

Browse files
authored
Merge pull request #872 from markshannon/python-bottle
Python: Add support for bottle framework.
2 parents 9dc3b93 + b644891 commit 1b25573

File tree

19 files changed

+486
-0
lines changed

19 files changed

+486
-0
lines changed

change-notes/1.20/analysis-python.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ Removes false positives seen when using Python 3.6, but not when using earlier v
3535
## Changes to QL libraries
3636

3737
* Added support for the `dill` pickle library.
38+
* Added support for the bottle web framework.
39+

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ import semmle.python.web.django.Redirect
66
import semmle.python.web.flask.Redirect
77
import semmle.python.web.tornado.Redirect
88
import semmle.python.web.pyramid.Redirect
9+
import semmle.python.web.bottle.Redirect

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ import semmle.python.web.flask.Request
33
import semmle.python.web.tornado.Request
44
import semmle.python.web.pyramid.Request
55
import semmle.python.web.twisted.Request
6+
import semmle.python.web.bottle.Request

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ import semmle.python.web.flask.Response
33
import semmle.python.web.pyramid.Response
44
import semmle.python.web.tornado.Response
55
import semmle.python.web.twisted.Response
6+
import semmle.python.web.bottle.Response
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import python
2+
import semmle.python.web.Http
3+
import semmle.python.types.Extensions
4+
5+
/** The bottle module */
6+
ModuleObject theBottleModule() {
7+
result = ModuleObject::named("bottle")
8+
}
9+
10+
/** The bottle.Bottle class */
11+
ClassObject theBottleClass() {
12+
result = ModuleObject::named("bottle").getAttribute("Bottle")
13+
}
14+
15+
/** Holds if `route` is routed to `func`
16+
* by decorating `func` with `app.route(route)` or `route(route)`
17+
*/
18+
predicate bottle_route(CallNode route_call, ControlFlowNode route, Function func) {
19+
exists(CallNode decorator_call, string name |
20+
route_call.getFunction().(AttrNode).getObject(name).refersTo(_, theBottleClass(), _) or
21+
route_call.getFunction().refersTo(theBottleModule().getAttribute(name))
22+
|
23+
(name = "route" or name = httpVerbLower()) and
24+
decorator_call.getFunction() = route_call and
25+
route_call.getArg(0) = route and
26+
decorator_call.getArg(0).getNode().(FunctionExpr).getInnerScope() = func
27+
)
28+
}
29+
30+
class BottleRoute extends ControlFlowNode {
31+
32+
BottleRoute() {
33+
bottle_route(this, _, _)
34+
}
35+
36+
string getUrl() {
37+
exists(StrConst url |
38+
bottle_route(this, url.getAFlowNode(), _) and
39+
result = url.getText()
40+
)
41+
}
42+
43+
Function getFunction() {
44+
bottle_route(this, _, result)
45+
}
46+
47+
Parameter getNamedArgument() {
48+
exists(string name, Function func |
49+
func = this.getFunction() and
50+
func.getArgByName(name) = result and
51+
this.getUrl().matches("%<" + name + ">%")
52+
)
53+
}
54+
}
55+
56+
57+
/* bottle module route constants */
58+
59+
class BottleRoutePointToExtension extends CustomPointsToFact {
60+
61+
string name;
62+
63+
BottleRoutePointToExtension() {
64+
exists(DefinitionNode defn |
65+
defn.getScope().(Module).getName() = "bottle" and
66+
this = defn.getValue() and
67+
name = defn.(NameNode).getId()
68+
|
69+
name = "route" or
70+
name = httpVerbLower()
71+
)
72+
}
73+
74+
override predicate pointsTo(Context context, Object value, ClassObject cls, ControlFlowNode origin) {
75+
context.isImport() and
76+
ModuleObject::named("bottle").getAttribute("Bottle").(ClassObject).attributeRefersTo(name, value, cls, origin)
77+
}
78+
}
79+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/** Provides class representing the `bottle.redirect` function.
2+
* This module is intended to be imported into a taint-tracking query
3+
* to extend `TaintSink`.
4+
*/
5+
import python
6+
7+
import semmle.python.security.TaintTracking
8+
import semmle.python.security.strings.Basic
9+
import semmle.python.web.bottle.General
10+
11+
FunctionObject bottle_redirect() {
12+
result = theBottleModule().getAttribute("redirect")
13+
}
14+
15+
/**
16+
* Represents an argument to the `bottle.redirect` function.
17+
*/
18+
class BottleRedirect extends TaintSink {
19+
20+
override string toString() {
21+
result = "bottle.redirect"
22+
}
23+
24+
BottleRedirect() {
25+
exists(CallNode call |
26+
bottle_redirect().getACall() = call and
27+
this = call.getAnArg()
28+
)
29+
}
30+
31+
override predicate sinks(TaintKind kind) {
32+
kind instanceof StringKind
33+
}
34+
35+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import python
2+
3+
4+
import semmle.python.security.TaintTracking
5+
import semmle.python.security.strings.Untrusted
6+
import semmle.python.web.Http
7+
import semmle.python.web.bottle.General
8+
9+
private Object theBottleRequestObject() {
10+
result = theBottleModule().getAttribute("request")
11+
}
12+
13+
class BottleRequestKind extends TaintKind {
14+
15+
BottleRequestKind() {
16+
this = "bottle.request"
17+
}
18+
19+
override TaintKind getTaintOfAttribute(string name) {
20+
result instanceof BottleFormsDict and
21+
(name = "cookies" or name = "query" or name = "form")
22+
or
23+
result instanceof UntrustedStringKind and
24+
(name = "query_string" or name = "url_args")
25+
or
26+
result.(DictKind).getValue() instanceof FileUpload and
27+
name = "files"
28+
}
29+
30+
}
31+
32+
private class RequestSource extends TaintSource {
33+
34+
RequestSource() {
35+
this.(ControlFlowNode).refersTo(theBottleRequestObject())
36+
}
37+
38+
override predicate isSourceOf(TaintKind kind) {
39+
kind instanceof BottleRequestKind
40+
}
41+
42+
}
43+
44+
45+
class BottleFormsDict extends TaintKind {
46+
47+
BottleFormsDict() {
48+
this = "bottle.FormsDict"
49+
}
50+
51+
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
52+
/* Cannot use `getTaintOfAttribute(name)` as it wouldn't bind `name` */
53+
exists(string name |
54+
fromnode = tonode.(AttrNode).getObject(name) and
55+
result instanceof UntrustedStringKind
56+
|
57+
name != "get" and name != "getunicode" and name != "getall"
58+
)
59+
}
60+
61+
override TaintKind getTaintOfMethodResult(string name) {
62+
(name = "get" or name = "getunicode") and
63+
result instanceof UntrustedStringKind
64+
or
65+
name = "getall" and result.(SequenceKind).getItem() instanceof UntrustedStringKind
66+
}
67+
}
68+
69+
class FileUpload extends TaintKind {
70+
71+
FileUpload() {
72+
this = "bottle.FileUpload"
73+
}
74+
75+
override TaintKind getTaintOfAttribute(string name) {
76+
name = "filename" and result instanceof UntrustedStringKind
77+
or
78+
name = "raw_filename" and result instanceof UntrustedStringKind
79+
or
80+
name = "file" and result instanceof UntrustedFile
81+
}
82+
83+
}
84+
85+
class UntrustedFile extends TaintKind {
86+
87+
UntrustedFile() { this = "Untrusted file" }
88+
89+
}
90+
91+
//
92+
// TO DO.. File uploads -- Should check about file uploads for other frameworks as well.
93+
// Move UntrustedFile to shared location
94+
//
95+
96+
97+
/** Parameter to a bottle request handler function */
98+
class BottleRequestParameter extends TaintSource {
99+
100+
BottleRequestParameter() {
101+
exists(BottleRoute route |
102+
route.getNamedArgument() = this.(ControlFlowNode).getNode()
103+
)
104+
}
105+
106+
override predicate isSourceOf(TaintKind kind) {
107+
kind instanceof UntrustedStringKind
108+
}
109+
110+
override string toString() {
111+
result = "bottle handler function argument"
112+
}
113+
114+
}
115+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import python
2+
3+
import semmle.python.security.TaintTracking
4+
import semmle.python.security.strings.Untrusted
5+
import semmle.python.web.Http
6+
import semmle.python.web.bottle.General
7+
8+
9+
/** A bottle.Response object
10+
* This isn't really a "taint", but we use the value tracking machinery to
11+
* track the flow of response objects.
12+
*/
13+
class BottleResponse extends TaintKind {
14+
15+
BottleResponse() {
16+
this = "bottle.response"
17+
}
18+
19+
}
20+
21+
private Object theBottleResponseObject() {
22+
result = theBottleModule().getAttribute("response")
23+
}
24+
25+
class BottleResponseBodyAssignment extends TaintSink {
26+
27+
BottleResponseBodyAssignment() {
28+
exists(DefinitionNode lhs |
29+
lhs.getValue() = this and
30+
lhs.(AttrNode).getObject("body").refersTo(theBottleResponseObject())
31+
)
32+
}
33+
34+
override predicate sinks(TaintKind kind) {
35+
kind instanceof UntrustedStringKind
36+
}
37+
38+
}
39+
40+
class BottleHandlerFunctionResult extends TaintSink {
41+
42+
BottleHandlerFunctionResult() {
43+
exists(BottleRoute route, Return ret |
44+
ret.getScope() = route.getFunction() and
45+
ret.getValue().getAFlowNode() = this
46+
)
47+
}
48+
49+
override predicate sinks(TaintKind kind) {
50+
kind instanceof UntrustedStringKind
51+
}
52+
53+
override string toString() {
54+
result = "bottle handler function result"
55+
}
56+
57+
}
58+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
| /args | test.py:31:1:31:14 | Function unsafe2 |
2+
| /bye/<name> | test.py:12:1:12:25 | Function bye |
3+
| /hello/<name> | test.py:8:1:8:27 | Function hello |
4+
| /other | test.py:17:1:17:12 | Function other |
5+
| /wrong/<where> | test.py:27:1:27:31 | Function unsafe |
6+
| /wrong/url | test.py:23:1:23:11 | Function safe |
7+
| /xss | test.py:35:1:35:16 | Function maybe_xss |
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import python
2+
3+
import semmle.python.web.bottle.General
4+
5+
from BottleRoute route
6+
7+
select route.getUrl(), route.getFunction()

0 commit comments

Comments
 (0)