Skip to content

Commit cef845a

Browse files
author
edvraa
committed
Support string expressions
1 parent ea38f0d commit cef845a

File tree

3 files changed

+83
-25
lines changed

3 files changed

+83
-25
lines changed

javascript/ql/src/experimental/semmle/javascript/security/InsecureCookie.qll

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,31 @@ module Cookie {
4545
* Holds if the cookie is authentication sensitive and lacks HttpOnly.
4646
*/
4747
abstract predicate isAuthNotHttpOnly();
48+
}
4849

49-
/**
50-
* Holds if the expression is a variable with a sensitive name.
51-
*/
52-
predicate isAuthVariable(DataFlow::Node expr) {
53-
exists(string val |
54-
(
55-
val = expr.getStringValue() or
56-
val = expr.asExpr().(VarAccess).getName() or
57-
val = expr.(DataFlow::PropRead).getPropertyName()
58-
) and
59-
regexpMatchAuth(val)
60-
)
61-
or
62-
isAuthVariable(expr.getAPredecessor())
63-
}
50+
/**
51+
* Holds if the expression is a variable with a sensitive name.
52+
*/
53+
private predicate isAuthVariable(DataFlow::Node expr) {
54+
exists(string val |
55+
(
56+
val = expr.getStringValue() or
57+
val = expr.asExpr().(VarAccess).getName() or
58+
val = expr.(DataFlow::PropRead).getPropertyName()
59+
) and
60+
regexpMatchAuth(val)
61+
)
62+
or
63+
isAuthVariable(expr.getAPredecessor())
64+
}
6465

65-
/**
66-
* Holds if the string contains sensitive auth keyword, but not antiforgery token.
67-
*/
68-
bindingset[val]
69-
predicate regexpMatchAuth(string val) {
70-
val.regexpMatch("(?i).*(session|login|token|user|auth|credential).*") and
71-
not val.regexpMatch("(?i).*(xsrf|csrf|forgery).*")
72-
}
66+
/**
67+
* Holds if the string contains sensitive auth keyword, but not antiforgery token.
68+
*/
69+
bindingset[val]
70+
private predicate regexpMatchAuth(string val) {
71+
val.regexpMatch("(?i).*(session|login|token|user|auth|credential).*") and
72+
not val.regexpMatch("(?i).*(xsrf|csrf|forgery).*")
7373
}
7474

7575
/**
@@ -168,6 +168,26 @@ module Cookie {
168168
}
169169
}
170170

171+
private class AttributeToSetCookieHeaderTrackingConfig extends TaintTracking::Configuration {
172+
AttributeToSetCookieHeaderTrackingConfig() { this = "AttributeToSetCookieHeaderTrackingConfig" }
173+
174+
override predicate isSource(DataFlow::Node source) {
175+
exists(string s | source.mayHaveStringValue(s))
176+
}
177+
178+
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof TemplateLiteral }
179+
}
180+
181+
private class SensitiveNameToSetCookieHeaderTrackingConfig extends TaintTracking::Configuration {
182+
SensitiveNameToSetCookieHeaderTrackingConfig() {
183+
this = "SensitiveNameToSetCookieHeaderTrackingConfig"
184+
}
185+
186+
override predicate isSource(DataFlow::Node source) { isAuthVariable(source) }
187+
188+
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof TemplateLiteral }
189+
}
190+
171191
/**
172192
* A cookie set using `Set-Cookie` header of an `HTTP` response.
173193
* (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
@@ -197,7 +217,7 @@ module Cookie {
197217
* A cookie is httpOnly if the `httpOnly` flag is specified in the cookie definition.
198218
* The default is `false`.
199219
*/
200-
override predicate isHttpOnly() { allHaveCookieAttribute("httponly") }
220+
override predicate isHttpOnly() { allHaveCookieAttribute(httpOnlyFlag()) }
201221

202222
/**
203223
* The predicate holds only if all elements have the specified attribute.
@@ -209,6 +229,14 @@ module Cookie {
209229
n.mayHaveStringValue(s) and
210230
hasCookieAttribute(s, attribute)
211231
)
232+
or
233+
exists(AttributeToSetCookieHeaderTrackingConfig cfg, DataFlow::Node source |
234+
cfg.hasFlow(source, n) and
235+
exists(string attr |
236+
source.mayHaveStringValue(attr) and
237+
attr.regexpMatch("(?i).*\\b" + attribute + "\\b.*")
238+
)
239+
)
212240
)
213241
}
214242

@@ -221,10 +249,19 @@ module Cookie {
221249
exists(string s |
222250
n.mayHaveStringValue(s) and
223251
(
224-
not hasCookieAttribute(s, "httponly") and
252+
not hasCookieAttribute(s, httpOnlyFlag()) and
225253
regexpMatchAuth(getCookieName(s))
226254
)
227255
)
256+
or
257+
not exists(AttributeToSetCookieHeaderTrackingConfig cfg, DataFlow::Node source |
258+
cfg.hasFlow(source, n) and
259+
exists(string attr |
260+
source.mayHaveStringValue(attr) and
261+
attr.regexpMatch("(?i).*\\b" + httpOnlyFlag() + "\\b.*")
262+
)
263+
) and
264+
exists(SensitiveNameToSetCookieHeaderTrackingConfig cfg | cfg.hasFlow(_, n))
228265
)
229266
}
230267

javascript/ql/test/query-tests/Security/CWE-1004/CookieWithoutHttpOnly.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
| test_httpserver.js:7:37:7:48 | "auth=ninja" | Cookie attribute 'HttpOnly' is not set to true. |
99
| test_httpserver.js:27:37:27:70 | ["auth= ... cript"] | Cookie attribute 'HttpOnly' is not set to true. |
1010
| test_httpserver.js:57:37:57:80 | ["auth= ... cript"] | Cookie attribute 'HttpOnly' is not set to true. |
11+
| test_httpserver.js:87:37:87:59 | `sessio ... {attr}` | Cookie attribute 'HttpOnly' is not set to true. |
1112
| test_responseCookie.js:15:5:20:10 | res.coo ... }) | Cookie attribute 'HttpOnly' is not set to true. |
1213
| test_responseCookie.js:25:5:28:10 | res.coo ... }) | Cookie attribute 'HttpOnly' is not set to true. |
1314
| test_responseCookie.js:48:5:48:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true. |

javascript/ql/test/query-tests/Security/CWE-1004/test_httpserver.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,24 @@ function test7() {
6868
res.writeHead(200, { 'Content-Type': 'text/plain' });
6969
res.end('ok');
7070
});
71+
}
72+
73+
function test8() {
74+
const server = http.createServer((req, res) => {
75+
res.setHeader('Content-Type', 'text/html');
76+
let attr = "; httponly"
77+
res.setHeader("Set-Cookie", `session=ninja ${attr}`); // Good, httponly string expression
78+
res.writeHead(200, { 'Content-Type': 'text/plain' });
79+
res.end('ok');
80+
});
81+
}
82+
83+
function test9() {
84+
const server = http.createServer((req, res) => {
85+
res.setHeader('Content-Type', 'text/html');
86+
let attr = "; secure"
87+
res.setHeader("Set-Cookie", `session=ninja ${attr}`); // Bad, not httponly string expression
88+
res.writeHead(200, { 'Content-Type': 'text/plain' });
89+
res.end('ok');
90+
});
7191
}

0 commit comments

Comments
 (0)