Skip to content

Commit 4963caf

Browse files
committed
Rewrite frameworks modeling
1 parent 066504e commit 4963caf

File tree

4 files changed

+186
-0
lines changed

4 files changed

+186
-0
lines changed

python/ql/src/experimental/semmle/python/Frameworks.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
*/
44

55
private import experimental.semmle.python.frameworks.Stdlib
6+
private import experimental.semmle.python.frameworks.Flask
7+
private import experimental.semmle.python.frameworks.Django
8+
private import experimental.semmle.python.frameworks.Werkzeug
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `django` PyPI package.
3+
* See https://www.djangoproject.com/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.frameworks.Django
8+
private import semmle.python.dataflow.new.DataFlow
9+
private import experimental.semmle.python.Concepts
10+
private import semmle.python.ApiGraphs
11+
12+
private module PrivateDjango {
13+
API::Node django() { result = API::moduleImport("django") }
14+
15+
private module django {
16+
API::Node http() { result = django().getMember("http") }
17+
18+
module http {
19+
API::Node response() { result = http().getMember("response") }
20+
21+
module response {
22+
module HttpResponse {
23+
API::Node baseClassRef() {
24+
result = response().getMember("HttpResponse").getReturn()
25+
or
26+
// Handle `django.http.HttpResponse` alias
27+
result = http().getMember("HttpResponse").getReturn()
28+
}
29+
30+
/** Gets a reference to a header instance. */
31+
private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
32+
t.start() and
33+
(
34+
exists(SubscriptNode subscript |
35+
subscript.getObject() = baseClassRef().getAUse().asCfgNode() and
36+
result.asCfgNode() = subscript
37+
)
38+
or
39+
result.(DataFlow::AttrRead).getObject() = baseClassRef().getAUse()
40+
)
41+
or
42+
exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
43+
}
44+
45+
/** Gets a reference to a header instance use. */
46+
private DataFlow::Node headerInstance() {
47+
headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
48+
}
49+
50+
/** Gets a reference to a header instance call with `__setitem__`. */
51+
private DataFlow::Node headerSetItemCall() {
52+
result = headerInstance() and
53+
result.(DataFlow::AttrRead).getAttributeName() = "__setitem__"
54+
}
55+
56+
class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
57+
DjangoResponseSetItemCall() { this.getFunction() = headerSetItemCall() }
58+
59+
override DataFlow::Node getHeaderInput() { result = this.getArg([0, 1]) }
60+
}
61+
62+
class DjangoResponseDefinition extends DataFlow::Node, HeaderDeclaration::Range {
63+
DataFlow::Node headerInput;
64+
65+
DjangoResponseDefinition() {
66+
this.asCfgNode().(DefinitionNode) = headerInstance().asCfgNode() and
67+
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
68+
}
69+
70+
override DataFlow::Node getHeaderInput() {
71+
result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `flask` PyPI package.
3+
* See https://flask.palletsprojects.com/en/1.1.x/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.frameworks.Flask
8+
private import semmle.python.dataflow.new.DataFlow
9+
private import experimental.semmle.python.Concepts
10+
private import semmle.python.ApiGraphs
11+
12+
module ExperimentalFlask {
13+
/**
14+
* A reference to either `flask.make_response` function, or the `make_response` method on
15+
* an instance of `flask.Flask`. This creates an instance of the `flask_response`
16+
* class (class-attribute on a flask application), which by default is
17+
* `flask.Response`.
18+
*
19+
* See
20+
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
21+
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
22+
*/
23+
private API::Node flaskMakeResponse() {
24+
result in [
25+
API::moduleImport("flask").getMember("make_response"),
26+
Flask::FlaskApp::instance().getMember("make_response")
27+
]
28+
}
29+
30+
/** Gets a reference to a header instance. */
31+
private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
32+
t.start() and
33+
result.(DataFlow::AttrRead).getObject().getALocalSource() =
34+
[Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAUse()
35+
or
36+
exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
37+
}
38+
39+
/** Gets a reference to a header instance use. */
40+
private DataFlow::Node headerInstance() {
41+
headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
42+
}
43+
44+
/** Gets a reference to a header instance call/subscript */
45+
private DataFlow::Node headerInstanceCall() {
46+
headerInstance() in [result.(DataFlow::AttrRead), result.(DataFlow::AttrRead).getObject()] or
47+
headerInstance().asExpr() = result.asExpr().(Subscript).getObject()
48+
}
49+
50+
class FlaskHeaderDefinition extends DataFlow::Node, HeaderDeclaration::Range {
51+
DataFlow::Node headerInput;
52+
53+
FlaskHeaderDefinition() {
54+
this.asCfgNode().(DefinitionNode) = headerInstanceCall().asCfgNode() and
55+
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
56+
}
57+
58+
override DataFlow::Node getHeaderInput() {
59+
result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
60+
}
61+
}
62+
63+
private class FlaskMakeResponseExtend extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
64+
FlaskMakeResponseExtend() { this.getFunction() = headerInstanceCall() }
65+
66+
override DataFlow::Node getHeaderInput() { result = this.getArg(0) }
67+
}
68+
69+
private class FlaskResponse extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
70+
FlaskResponse() { this = Flask::Response::classRef().getACall() }
71+
72+
override DataFlow::Node getHeaderInput() { result = this.getArgByName("headers") }
73+
}
74+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `Werkzeug` PyPI package.
3+
* See
4+
* - https://pypi.org/project/Werkzeug/
5+
* - https://werkzeug.palletsprojects.com/en/1.0.x/#werkzeug
6+
*/
7+
8+
private import python
9+
private import semmle.python.frameworks.Flask
10+
private import semmle.python.dataflow.new.DataFlow
11+
private import experimental.semmle.python.Concepts
12+
private import semmle.python.ApiGraphs
13+
14+
private module Werkzeug {
15+
module datastructures {
16+
module Headers {
17+
class WerkzeugHeaderAddCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
18+
WerkzeugHeaderAddCall() {
19+
this.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() =
20+
API::moduleImport("werkzeug")
21+
.getMember("datastructures")
22+
.getMember("Headers")
23+
.getACall() and
24+
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "add"
25+
}
26+
27+
override DataFlow::Node getHeaderInput() { result = this.getArg(_) }
28+
}
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)