Skip to content

Commit 6ecb6d1

Browse files
committed
Adapt Django and Flask to their main modelings
1 parent e7d649f commit 6ecb6d1

File tree

2 files changed

+71
-25
lines changed

2 files changed

+71
-25
lines changed

python/ql/src/experimental/semmle/python/frameworks/Django.qll

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
99
private import experimental.semmle.python.Concepts
1010
private import semmle.python.ApiGraphs
1111
import semmle.python.dataflow.new.RemoteFlowSources
12+
private import semmle.python.Concepts
1213

1314
private module PrivateDjango {
1415
private module django {
@@ -32,22 +33,64 @@ private module PrivateDjango {
3233
module response {
3334
module HttpResponse {
3435
API::Node baseClassRef() {
35-
result = response().getMember("HttpResponse").getReturn()
36+
result = response().getMember("HttpResponse")
3637
or
3738
// Handle `django.http.HttpResponse` alias
38-
result = http().getMember("HttpResponse").getReturn()
39+
result = http().getMember("HttpResponse")
3940
}
4041

42+
/** Gets a reference to the `django.http.response.HttpResponse` class. */
43+
API::Node classRef() { result = baseClassRef().getASubclass*() }
44+
45+
/**
46+
* A source of instances of `django.http.response.HttpResponse`, extend this class to model new instances.
47+
*
48+
* This can include instantiations of the class, return values from function
49+
* calls, or a special parameter that will be set when functions are called by an external
50+
* library.
51+
*
52+
* Use the predicate `HttpResponse::instance()` to get references to instances of `django.http.response.HttpResponse`.
53+
*/
54+
abstract class InstanceSource extends HTTP::Server::HttpResponse::Range, DataFlow::Node {
55+
}
56+
57+
/** A direct instantiation of `django.http.response.HttpResponse`. */
58+
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
59+
ClassInstantiation() { this = classRef().getACall() }
60+
61+
override DataFlow::Node getBody() {
62+
result in [this.getArg(0), this.getArgByName("content")]
63+
}
64+
65+
// How to support the `headers` argument here?
66+
override DataFlow::Node getMimetypeOrContentTypeArg() {
67+
result in [this.getArg(1), this.getArgByName("content_type")]
68+
}
69+
70+
override string getMimetypeDefault() { result = "text/html" }
71+
}
72+
73+
/** Gets a reference to an instance of `django.http.response.HttpResponse`. */
74+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
75+
t.start() and
76+
result instanceof InstanceSource
77+
or
78+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
79+
}
80+
81+
/** Gets a reference to an instance of `django.http.response.HttpResponse`. */
82+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
83+
4184
/** Gets a reference to a header instance. */
4285
private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
4386
t.start() and
4487
(
4588
exists(SubscriptNode subscript |
46-
subscript.getObject() = baseClassRef().getAUse().asCfgNode() and
89+
subscript.getObject() = baseClassRef().getReturn().getAUse().asCfgNode() and
4790
result.asCfgNode() = subscript
4891
)
4992
or
50-
result.(DataFlow::AttrRead).getObject() = baseClassRef().getAUse()
93+
result.(DataFlow::AttrRead).getObject() = baseClassRef().getReturn().getAUse()
5194
)
5295
or
5396
exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
@@ -106,27 +149,35 @@ private module PrivateDjango {
106149
* * `isHttpOnly()` predicate would succeed.
107150
* * `isSameSite()` predicate would succeed.
108151
*/
109-
class DjangoSetCookieCall extends DataFlow::CallCfgNode, Cookie::Range {
110-
DjangoSetCookieCall() { this = baseClassRef().getMember("set_cookie").getACall() }
152+
class DjangoResponseSetCookieCall extends DataFlow::MethodCallNode, Cookie::Range {
153+
DjangoResponseSetCookieCall() {
154+
this.calls(django::http::response::HttpResponse::instance(), "set_cookie")
155+
}
111156

112-
override DataFlow::Node getNameArg() { result = this.getArg(0) }
157+
override DataFlow::Node getNameArg() {
158+
result in [this.getArg(0), this.getArgByName("key")]
159+
}
113160

114-
override DataFlow::Node getValueArg() { result = this.getArgByName("value") }
161+
override DataFlow::Node getValueArg() {
162+
result in [this.getArg(1), this.getArgByName("value")]
163+
}
115164

116165
override predicate isSecure() {
117166
DataFlow::exprNode(any(True t))
118167
.(DataFlow::LocalSourceNode)
119-
.flowsTo(this.getArgByName("secure"))
168+
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("secure"))
120169
}
121170

122171
override predicate isHttpOnly() {
123172
DataFlow::exprNode(any(True t))
124173
.(DataFlow::LocalSourceNode)
125-
.flowsTo(this.getArgByName("httponly"))
174+
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("httponly"))
126175
}
127176

128177
override predicate isSameSite() {
129-
this.getArgByName("samesite").asExpr().(Str_).getS() in ["Strict", "Lax"]
178+
this.(DataFlow::CallCfgNode).getArgByName("samesite").asExpr().(Str_).getS() in [
179+
"Strict", "Lax"
180+
]
130181
}
131182

132183
override DataFlow::Node getHeaderArg() { none() }

python/ql/src/experimental/semmle/python/frameworks/Flask.qll

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private import semmle.python.frameworks.Flask
88
private import semmle.python.dataflow.new.DataFlow
99
private import experimental.semmle.python.Concepts
1010
private import semmle.python.ApiGraphs
11+
private import semmle.python.frameworks.Flask
1112

1213
module ExperimentalFlask {
1314
/**
@@ -102,33 +103,27 @@ module ExperimentalFlask {
102103
* * `isHttpOnly()` predicate would succeed.
103104
* * `isSameSite()` predicate would succeed.
104105
*/
105-
class FlaskSetCookieCall extends DataFlow::CallCfgNode, Cookie::Range {
106-
FlaskSetCookieCall() {
107-
this =
108-
[Flask::Response::classRef(), flaskMakeResponse()]
109-
.getReturn()
110-
.getMember("set_cookie")
111-
.getACall()
112-
}
113-
114-
override DataFlow::Node getNameArg() { result = this.getArg(0) }
106+
class FlaskSetCookieCall extends Cookie::Range instanceof Flask::FlaskResponseSetCookieCall {
107+
override DataFlow::Node getNameArg() { result = this.getNameArg() }
115108

116-
override DataFlow::Node getValueArg() { result = this.getArgByName("value") }
109+
override DataFlow::Node getValueArg() { result = this.getValueArg() }
117110

118111
override predicate isSecure() {
119112
DataFlow::exprNode(any(True t))
120113
.(DataFlow::LocalSourceNode)
121-
.flowsTo(this.getArgByName("secure"))
114+
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("secure"))
122115
}
123116

124117
override predicate isHttpOnly() {
125118
DataFlow::exprNode(any(True t))
126119
.(DataFlow::LocalSourceNode)
127-
.flowsTo(this.getArgByName("httponly"))
120+
.flowsTo(this.(DataFlow::CallCfgNode).getArgByName("httponly"))
128121
}
129122

130123
override predicate isSameSite() {
131-
this.getArgByName("samesite").asExpr().(Str_).getS() in ["Strict", "Lax"]
124+
this.(DataFlow::CallCfgNode).getArgByName("samesite").asExpr().(Str_).getS() in [
125+
"Strict", "Lax"
126+
]
132127
}
133128

134129
override DataFlow::Node getHeaderArg() { none() }

0 commit comments

Comments
 (0)