Skip to content

Commit a9d43a2

Browse files
committed
Python: Modernise flask library
1 parent edfcf39 commit a9d43a2

5 files changed

Lines changed: 25 additions & 30 deletions

File tree

python/ql/src/Security/CWE-215/FlaskDebug.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import semmle.python.web.flask.General
1717

1818
from CallNode call, Object isTrue
1919
where
20-
call = theFlaskClass().declaredAttribute("run").(FunctionObject).getACall() and
20+
call = theFlaskClass().declaredAttribute("run").(FunctionValue).getACall() and
2121
call.getArgByName("debug").refersTo(isTrue) and
2222
isTrue.booleanValue() = true
2323
select call, "A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger."

python/ql/src/semmle/python/web/flask/General.qll

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,26 @@
11
import python
22
import semmle.python.web.Http
33

4-
/** The flask module */
5-
ModuleObject theFlaskModule() {
6-
result = ModuleObject::named("flask")
7-
}
8-
94
/** The flask app class */
10-
ClassObject theFlaskClass() {
11-
result = theFlaskModule().attr("Flask")
5+
ClassValue theFlaskClass() {
6+
result = Value::named("flask.Flask")
127
}
138

149
/** The flask MethodView class */
15-
ClassObject theFlaskMethodViewClass() {
16-
result = ModuleObject::named("flask.views").attr("MethodView")
10+
ClassValue theFlaskMethodViewClass() {
11+
result = Value::named("flask.views.MethodView")
1712
}
1813

19-
ClassObject theFlaskReponseClass() {
20-
result = theFlaskModule().attr("Response")
14+
ClassValue theFlaskReponseClass() {
15+
result = Value::named("flask.Response")
2116
}
2217

2318
/** Holds if `route` is routed to `func`
2419
* by decorating `func` with `app.route(route)`
2520
*/
2621
predicate app_route(ControlFlowNode route, Function func) {
2722
exists(CallNode route_call, CallNode decorator_call |
28-
route_call.getFunction().(AttrNode).getObject("route").refersTo(_, theFlaskClass(), _) and
23+
route_call.getFunction().(AttrNode).getObject("route").pointsTo().getClass() = theFlaskClass() and
2924
decorator_call.getFunction() = route_call and
3025
route_call.getArg(0) = route and
3126
decorator_call.getArg(0).getNode().(FunctionExpr).getInnerScope() = func
@@ -35,7 +30,7 @@ predicate app_route(ControlFlowNode route, Function func) {
3530
/* Helper for add_url_rule */
3631
private predicate add_url_rule_call(ControlFlowNode regex, ControlFlowNode callable) {
3732
exists(CallNode call |
38-
call.getFunction().(AttrNode).getObject("add_url_rule").refersTo(_, theFlaskClass(), _) and
33+
call.getFunction().(AttrNode).getObject("add_url_rule").pointsTo().getClass() = theFlaskClass() and
3934
regex = call.getArg(0) |
4035
callable = call.getArg(2) or
4136
callable = call.getArgByName("view_func")
@@ -47,14 +42,14 @@ predicate add_url_rule(ControlFlowNode regex, Function func) {
4742
exists(ControlFlowNode callable |
4843
add_url_rule_call(regex, callable)
4944
|
50-
exists(PyFunctionObject f | f.getFunction() = func and callable.refersTo(f))
45+
exists(PythonFunctionValue f | f.getScope() = func and callable.pointsTo(f))
5146
or
5247
/* MethodView.as_view() */
5348
exists(MethodViewClass view_cls |
5449
view_cls.asTaint().taints(callable) |
55-
func = view_cls.lookupAttribute(httpVerbLower()).(FunctionObject).getFunction()
50+
func = view_cls.lookup(httpVerbLower()).(FunctionValue).getScope()
5651
)
57-
/* TO DO -- Handle Views that aren't MethodViews */
52+
/* TODO: -- Handle Views that aren't MethodViews */
5853
)
5954
}
6055

@@ -68,10 +63,10 @@ predicate flask_routing(ControlFlowNode regex, Function func) {
6863
}
6964

7065
/** A class that extends flask.views.MethodView */
71-
private class MethodViewClass extends ClassObject {
66+
private class MethodViewClass extends ClassValue {
7267

7368
MethodViewClass() {
74-
this.getAnImproperSuperType() = theFlaskMethodViewClass()
69+
this.getASuperType() = theFlaskMethodViewClass()
7570
}
7671

7772
/* As we are restricted to strings for taint kinds, we need to map these classes to strings. */
@@ -96,9 +91,9 @@ private class MethodViewTaint extends TaintKind {
9691
private class AsView extends TaintSource {
9792

9893
AsView() {
99-
exists(ClassObject view_class |
100-
view_class.getAnImproperSuperType() = theFlaskMethodViewClass() and
101-
this.(CallNode).getFunction().(AttrNode).getObject("as_view").refersTo(view_class)
94+
exists(ClassValue view_class |
95+
view_class.getASuperType() = theFlaskMethodViewClass() and
96+
this.(CallNode).getFunction().(AttrNode).getObject("as_view").pointsTo(view_class)
10297
)
10398
}
10499

@@ -109,7 +104,7 @@ private class AsView extends TaintSource {
109104
override predicate isSourceOf(TaintKind kind) {
110105
exists(MethodViewClass view_class |
111106
kind = view_class.asTaint() and
112-
this.(CallNode).getFunction().(AttrNode).getObject("as_view").refersTo(view_class)
107+
this.(CallNode).getFunction().(AttrNode).getObject("as_view").pointsTo(view_class)
113108
)
114109
}
115110

@@ -119,7 +114,7 @@ private class AsView extends TaintSource {
119114
class FlaskCookieSet extends CookieSet, CallNode {
120115

121116
FlaskCookieSet() {
122-
this.getFunction().(AttrNode).getObject("set_cookie").refersTo(_, theFlaskReponseClass(), _)
117+
this.getFunction().(AttrNode).getObject("set_cookie").pointsTo().getClass() = theFlaskReponseClass()
123118
}
124119

125120
override string toString() { result = CallNode.super.toString() }

python/ql/src/semmle/python/web/flask/Redirect.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import semmle.python.security.TaintTracking
88
import semmle.python.security.strings.Basic
99
import semmle.python.web.flask.General
1010

11-
FunctionObject flask_redirect() {
12-
result = theFlaskModule().attr("redirect")
11+
FunctionValue flask_redirect() {
12+
result = Value::named("flask.redirect")
1313
}
1414

1515
/**

python/ql/src/semmle/python/web/flask/Request.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import semmle.python.security.TaintTracking
44
import semmle.python.web.Http
55
import semmle.python.web.flask.General
66

7-
private Object theFlaskRequestObject() {
8-
result = theFlaskModule().attr("request")
7+
private Value theFlaskRequestObject() {
8+
result = Value::named("flask.request")
99

1010
}
1111

1212
/** Holds if `attr` is an access of attribute `name` of the flask request object */
1313
private predicate flask_request_attr(AttrNode attr, string name) {
1414
attr.isLoad() and
15-
attr.getObject(name).refersTo(theFlaskRequestObject())
15+
attr.getObject(name).pointsTo(theFlaskRequestObject())
1616
}
1717

1818
/** Source of external data from a flask request */

python/ql/src/semmle/python/web/flask/Response.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class FlaskResponseArgument extends HttpResponseTaintSink {
3232

3333
FlaskResponseArgument() {
3434
exists(CallNode call |
35-
call.getFunction().refersTo(theFlaskReponseClass()) and
35+
call.getFunction().pointsTo(theFlaskReponseClass()) and
3636
call.getArg(0) = this
3737
)
3838
}

0 commit comments

Comments
 (0)