Skip to content

Commit 92d59aa

Browse files
committed
refactor most of the isSensitive predicates into a common helper predicate
1 parent 834d5ec commit 92d59aa

File tree

1 file changed

+54
-60
lines changed

1 file changed

+54
-60
lines changed

javascript/ql/lib/semmle/javascript/frameworks/CookieLibraries.qll

Lines changed: 54 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,46 @@ module CookieWrites {
5656
string httpOnly() { result = "httpOnly" }
5757
}
5858

59+
/**
60+
* Holds if `node` looks like it can contain a sensitive cookie.
61+
* Either from `node` being a sensitive expression, or from `node` containing
62+
* a string value that looks like a sensitive cookie name.
63+
*/
64+
private predicate canHaveSensitiveCookie(DataFlow::Node node) {
65+
exists(string s |
66+
node.mayHaveStringValue(s) or
67+
s = node.(StringOps::ConcatenationRoot).getConstantStringParts()
68+
|
69+
HeuristicNames::nameIndicatesSensitiveData([s, getCookieName(s)], _)
70+
)
71+
or
72+
node.asExpr() instanceof SensitiveExpr
73+
}
74+
75+
/**
76+
* Gets cookie name from a `Set-Cookie` header value.
77+
* The header value always starts with `<cookie-name>=<cookie-value>` optionally followed by attributes:
78+
* `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
79+
*/
80+
bindingset[s]
81+
private string getCookieName(string s) {
82+
result = s.regexpCapture("\\s*\\b([^=\\s]*)\\b\\s*=.*", 1)
83+
}
84+
85+
/**
86+
* Holds if the `Set-Cookie` header value contains the specified attribute
87+
* 1. The attribute is case insensitive
88+
* 2. It always starts with a pair `<cookie-name>=<cookie-value>`.
89+
* If the attribute is present there must be `;` after the pair.
90+
* Other attributes like `Domain=`, `Path=`, etc. may come after the pair:
91+
* `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
92+
* See `https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie`
93+
*/
94+
bindingset[s, attribute]
95+
private predicate hasCookieAttribute(string s, string attribute) {
96+
s.regexpMatch("(?i).*;\\s*" + attribute + "\\b\\s*;?.*$")
97+
}
98+
5999
/**
60100
* A model of the `js-cookie` library (https://github.com/js-cookie/js-cookie).
61101
*/
@@ -90,11 +130,7 @@ private module JsCookie {
90130
this.getOptionArgument(2, CookieWrites::secure()).mayHaveBooleanValue(true)
91131
}
92132

93-
override predicate isSensitive() {
94-
HeuristicNames::nameIndicatesSensitiveData(any(string s |
95-
this.getArgument(0).mayHaveStringValue(s)
96-
), _)
97-
}
133+
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
98134
}
99135
}
100136

@@ -133,11 +169,7 @@ private module BrowserCookies {
133169
exists(DataFlow::moduleMember("browser-cookies", "defaults").getAPropertyWrite("secure"))
134170
}
135171

136-
override predicate isSensitive() {
137-
HeuristicNames::nameIndicatesSensitiveData(any(string s |
138-
this.getArgument(0).mayHaveStringValue(s)
139-
), _)
140-
}
172+
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
141173
}
142174
}
143175

@@ -173,11 +205,7 @@ private module LibCookie {
173205
this.getOptionArgument(2, CookieWrites::secure()).mayHaveBooleanValue(true)
174206
}
175207

176-
override predicate isSensitive() {
177-
HeuristicNames::nameIndicatesSensitiveData(any(string s |
178-
this.getArgument(0).mayHaveStringValue(s)
179-
), _)
180-
}
208+
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
181209
}
182210
}
183211

@@ -198,13 +226,7 @@ private module ExpressCookies {
198226
this.getOptionArgument(2, CookieWrites::secure()).mayHaveBooleanValue(true)
199227
}
200228

201-
override predicate isSensitive() {
202-
HeuristicNames::nameIndicatesSensitiveData(any(string s |
203-
this.getArgument(0).mayHaveStringValue(s)
204-
), _)
205-
or
206-
this.getArgument(0).asExpr() instanceof SensitiveExpr
207-
}
229+
override predicate isSensitive() { canHaveSensitiveCookie(this.getArgument(0)) }
208230

209231
override predicate isHttpOnly() {
210232
// A cookie is httpOnly if there are cookie options with the `httpOnly` flag set to `true`.
@@ -307,50 +329,24 @@ private class HTTPCookieWrite extends CookieWrites::CookieWrite {
307329
hasCookieAttribute(header, CookieWrites::httpOnly())
308330
}
309331

310-
override predicate isSensitive() {
311-
HeuristicNames::nameIndicatesSensitiveData(getCookieName(header), _)
312-
}
313-
}
314-
315-
/**
316-
* Gets cookie name from a `Set-Cookie` header value.
317-
* The header value always starts with `<cookie-name>=<cookie-value>` optionally followed by attributes:
318-
* `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
319-
*/
320-
bindingset[s]
321-
private string getCookieName(string s) {
322-
result = s.regexpCapture("\\s*\\b([^=\\s]*)\\b\\s*=.*", 1)
323-
}
324-
325-
/**
326-
* Holds if the `Set-Cookie` header value contains the specified attribute
327-
* 1. The attribute is case insensitive
328-
* 2. It always starts with a pair `<cookie-name>=<cookie-value>`.
329-
* If the attribute is present there must be `;` after the pair.
330-
* Other attributes like `Domain=`, `Path=`, etc. may come after the pair:
331-
* `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
332-
* See `https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie`
333-
*/
334-
bindingset[s, attribute]
335-
private predicate hasCookieAttribute(string s, string attribute) {
336-
s.regexpMatch("(?i).*;\\s*" + attribute + "\\b\\s*;?.*$")
332+
override predicate isSensitive() { canHaveSensitiveCookie(this) }
337333
}
338334

339335
/**
340336
* A write to `document.cookie`.
341337
*/
342338
private class DocumentCookieWrite extends CookieWrites::ClientSideCookieWrite {
343339
string cookie;
340+
DataFlow::PropWrite write;
344341

345342
DocumentCookieWrite() {
346-
exists(DataFlow::PropWrite write | this = write |
347-
write = DOM::documentRef().getAPropertyWrite("cookie") and
348-
cookie =
349-
[
350-
any(string s | write.getRhs().mayHaveStringValue(s)),
351-
write.getRhs().(StringOps::ConcatenationRoot).getConstantStringParts()
352-
]
353-
)
343+
this = write and
344+
write = DOM::documentRef().getAPropertyWrite("cookie") and
345+
cookie =
346+
[
347+
any(string s | write.getRhs().mayHaveStringValue(s)),
348+
write.getRhs().(StringOps::ConcatenationRoot).getConstantStringParts()
349+
]
354350
}
355351

356352
override predicate isSecure() {
@@ -359,7 +355,5 @@ private class DocumentCookieWrite extends CookieWrites::ClientSideCookieWrite {
359355
hasCookieAttribute(cookie, CookieWrites::secure())
360356
}
361357

362-
override predicate isSensitive() {
363-
HeuristicNames::nameIndicatesSensitiveData(getCookieName(cookie), _)
364-
}
358+
override predicate isSensitive() { canHaveSensitiveCookie(write.getRhs()) }
365359
}

0 commit comments

Comments
 (0)