Skip to content

Commit 48c3c3d

Browse files
committed
Broaden scope
1 parent 28ec8c9 commit 48c3c3d

5 files changed

Lines changed: 82 additions & 47 deletions

File tree

python/ql/src/experimental/Security/CWE-614/InsecureCookie.ql

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,16 @@
1212
// determine precision above
1313
import python
1414
import semmle.python.dataflow.new.DataFlow
15-
import semmle.python.Concepts
1615
import experimental.semmle.python.Concepts
1716

18-
from Expr cookieExpr
17+
from Cookie cookie, string alert
1918
where
20-
exists(HeaderDeclaration headerWrite, StrConst headerName, StrConst headerValue |
21-
headerName.getText() = "Set-Cookie" and
22-
DataFlow::exprNode(headerName).(DataFlow::LocalSourceNode).flowsTo(headerWrite.getNameArg()) and
23-
not headerValue.getText().regexpMatch(".*; *Secure;.*") and
24-
DataFlow::exprNode(headerValue).(DataFlow::LocalSourceNode).flowsTo(headerWrite.getValueArg()) and
25-
cookieExpr = headerWrite.asExpr()
26-
)
19+
cookie.isSecure() and
20+
alert = "secure"
2721
or
28-
exists(ExperimentalHTTP::CookieWrite cookieWrite, False f, None n |
29-
[DataFlow::exprNode(f), DataFlow::exprNode(n)]
30-
.(DataFlow::LocalSourceNode)
31-
.flowsTo(cookieWrite.(DataFlow::CallCfgNode).getArgByName("secure")) and
32-
cookieExpr = cookieWrite.asExpr()
33-
)
34-
select cookieExpr, "Cookie is added to response without the 'secure' flag being set."
22+
not cookie.isHttpOnly() and
23+
alert = "httponly"
24+
or
25+
cookie.isSameSite() and
26+
alert = "samesite"
27+
select cookie, "Cookie is added without the ", alert, " flag properly set."

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

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -301,54 +301,51 @@ class HeaderDeclaration extends DataFlow::Node {
301301
* A data-flow node that sets a cookie in an HTTP response.
302302
*
303303
* Extend this class to refine existing API models. If you want to model new APIs,
304-
* extend `HTTP::CookieWrite::Range` instead.
304+
* extend `Cookie::Range` instead.
305305
*/
306-
class CookieWrite extends DataFlow::Node {
307-
CookieWrite::Range range;
306+
class Cookie extends DataFlow::Node {
307+
Cookie::Range range;
308308

309-
CookieWrite() { this = range }
309+
Cookie() { this = range }
310310

311311
/**
312-
* Gets the argument, if any, specifying the raw cookie header.
312+
* Holds if this cookie is secure.
313313
*/
314-
DataFlow::Node getHeaderArg() { result = range.getHeaderArg() }
314+
predicate isSecure() { range.isSecure() }
315315

316316
/**
317-
* Gets the argument, if any, specifying the cookie name.
317+
* Holds if this cookie is HttpOnly.
318318
*/
319-
DataFlow::Node getNameArg() { result = range.getNameArg() }
319+
predicate isHttpOnly() { range.isHttpOnly() }
320320

321321
/**
322-
* Gets the argument, if any, specifying the cookie value.
322+
* Holds if the cookie is SameSite
323323
*/
324-
DataFlow::Node getValueArg() { result = range.getValueArg() }
324+
predicate isSameSite() { range.isSameSite() }
325325
}
326326

327327
/** Provides a class for modeling new cookie writes on HTTP responses. */
328-
module CookieWrite {
328+
module Cookie {
329329
/**
330330
* A data-flow node that sets a cookie in an HTTP response.
331331
*
332-
* Note: we don't require that this redirect must be sent to a client (a kind of
333-
* "if a tree falls in a forest and nobody hears it" situation).
334-
*
335332
* Extend this class to model new APIs. If you want to refine existing API models,
336-
* extend `HttpResponse` instead.
333+
* extend `Cookie` instead.
337334
*/
338335
abstract class Range extends DataFlow::Node {
339336
/**
340-
* Gets the argument, if any, specifying the raw cookie header.
337+
* Holds if this cookie is secure.
341338
*/
342-
abstract DataFlow::Node getHeaderArg();
339+
abstract predicate isSecure();
343340

344341
/**
345-
* Gets the argument, if any, specifying the cookie name.
342+
* Holds if this cookie is HttpOnly.
346343
*/
347-
abstract DataFlow::Node getNameArg();
344+
abstract predicate isHttpOnly();
348345

349346
/**
350-
* Gets the argument, if any, specifying the cookie value.
347+
* Holds if the cookie is SameSite.
351348
*/
352-
abstract DataFlow::Node getValueArg();
349+
abstract predicate isSameSite();
353350
}
354351
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Temporary: provides a class to extend current cookies to header declarations
3+
*/
4+
5+
import python
6+
import semmle.python.dataflow.new.DataFlow
7+
import semmle.python.dataflow.new.TaintTracking
8+
import experimental.semmle.python.Concepts
9+
10+
class CookieHeader extends HeaderDeclaration, Cookie::Range {
11+
CookieHeader() {
12+
this instanceof HeaderDeclaration and this.getNameArg().asExpr().(Str_).getS() = "Set-Cookie"
13+
}
14+
15+
override predicate isSecure() {
16+
this.getValueArg().asExpr().(Str_).getS().regexpMatch(".*; *Secure;.*")
17+
}
18+
19+
override predicate isHttpOnly() {
20+
this.getValueArg().asExpr().(Str_).getS().regexpMatch(".*; *HttpOnly;.*")
21+
}
22+
23+
override predicate isSameSite() {
24+
this.getValueArg().asExpr().(Str_).getS().regexpMatch(".*; *SameSite=(Strict|Lax);.*")
25+
}
26+
}

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,24 @@ private module PrivateDjango {
8787
override DataFlow::Node getValueArg() { result = headerInput }
8888
}
8989

90-
class DjangoSetCookieCall extends DataFlow::CallCfgNode,
91-
ExperimentalHTTP::CookieWrite::Range {
90+
class DjangoSetCookieCall extends DataFlow::CallCfgNode, Cookie::Range {
9291
DjangoSetCookieCall() { this = baseClassRef().getMember("set_cookie").getACall() }
9392

94-
override DataFlow::Node getHeaderArg() { none() }
93+
override predicate isSecure() {
94+
DataFlow::exprNode(any(True t))
95+
.(DataFlow::LocalSourceNode)
96+
.flowsTo(this.getArgByName("secure"))
97+
}
9598

96-
override DataFlow::Node getNameArg() { result = this.getArg(0) }
99+
override predicate isHttpOnly() {
100+
DataFlow::exprNode(any(True t))
101+
.(DataFlow::LocalSourceNode)
102+
.flowsTo(this.getArgByName("httponly"))
103+
}
97104

98-
override DataFlow::Node getValueArg() { result = this.getArg(1) }
105+
override predicate isSameSite() {
106+
this.getArgByName("samesite").asExpr().(Str_).getS() in ["Strict", "Lax"]
107+
}
99108
}
100109
}
101110
}

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,29 @@ module ExperimentalFlask {
8282
override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
8383
}
8484

85-
class DjangoSetCookieCall extends DataFlow::CallCfgNode, ExperimentalHTTP::CookieWrite::Range {
86-
DjangoSetCookieCall() {
85+
class FlaskSetCookieCall extends DataFlow::CallCfgNode, Cookie::Range {
86+
FlaskSetCookieCall() {
8787
this =
8888
[Flask::Response::classRef(), flaskMakeResponse()]
8989
.getReturn()
9090
.getMember("set_cookie")
9191
.getACall()
9292
}
9393

94-
override DataFlow::Node getHeaderArg() { none() }
94+
override predicate isSecure() {
95+
DataFlow::exprNode(any(True t))
96+
.(DataFlow::LocalSourceNode)
97+
.flowsTo(this.getArgByName("secure"))
98+
}
9599

96-
override DataFlow::Node getNameArg() { result = this.getArg(0) }
100+
override predicate isHttpOnly() {
101+
DataFlow::exprNode(any(True t))
102+
.(DataFlow::LocalSourceNode)
103+
.flowsTo(this.getArgByName("httponly"))
104+
}
97105

98-
override DataFlow::Node getValueArg() { result = this.getArg(1) }
106+
override predicate isSameSite() {
107+
this.getArgByName("samesite").asExpr().(Str_).getS() in ["Strict", "Lax"]
108+
}
99109
}
100110
}

0 commit comments

Comments
 (0)