55 */
66
77import javascript
8+ private import semmle.javascript.security.SensitiveActions
89
10+ // TODO: Move this entire file into stdlib.
11+ // TODO: make "session", "auth", a sensitive name.
12+ // TODO: Have helper predicate that selects the relevant Sensitive Classifications.
13+ // TODO: Look for more cookie libraries.
914module Cookie {
1015 /**
1116 * `secure` property of the cookie options.
@@ -18,70 +23,32 @@ module Cookie {
1823 string httpOnlyFlag ( ) { result = "httpOnly" }
1924
2025 /**
21- * Abstract class to represent different cases of insecure cookie settings .
26+ * A write to a cookie.
2227 */
23- abstract class Cookie extends DataFlow:: Node {
28+ abstract class CookieWrite extends DataFlow:: Node {
2429 /**
25- * Gets the name of the middleware/library used to set the cookie.
26- */
27- abstract string getKind ( ) ;
28-
29- /**
30- * Gets the options used to set this cookie, if any.
31- */
32- abstract DataFlow:: Node getCookieOptionsArgument ( ) ;
33-
34- /**
35- * Holds if this cookie is secure.
30+ * Holds if this cookie is secure, i.e. only transmitted over SSL.
3631 */
3732 abstract predicate isSecure ( ) ;
3833
3934 /**
40- * Holds if this cookie is HttpOnly.
35+ * Holds if this cookie is HttpOnly, i.e. not accessible by JavaScript .
4136 */
4237 abstract predicate isHttpOnly ( ) ;
4338
4439 /**
45- * Holds if the cookie is authentication sensitive and lacks HttpOnly .
40+ * Holds if the cookie is likely an authentication cookie or otherwise sensitive .
4641 */
47- abstract predicate isAuthNotHttpOnly ( ) ;
48- }
49-
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- }
65-
66- /**
67- * Holds if `val` looks related to authentication, without being an anti-forgery 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).*" )
42+ abstract predicate isSensitive ( ) ;
7343 }
7444
7545 /**
7646 * A cookie set using the `express` module `cookie-session` (https://github.com/expressjs/cookie-session).
7747 */
78- class InsecureCookieSession extends ExpressLibraries:: CookieSession:: MiddlewareInstance , Cookie {
79- override string getKind ( ) { result = "cookie-session" }
80-
81- override DataFlow:: SourceNode getCookieOptionsArgument ( ) { result .flowsTo ( getArgument ( 0 ) ) }
82-
48+ class InsecureCookieSession extends ExpressLibraries:: CookieSession:: MiddlewareInstance ,
49+ CookieWrite {
8350 private DataFlow:: Node getCookieFlagValue ( string flag ) {
84- result = this .getCookieOptionsArgument ( ) . getAPropertyWrite ( flag ) . getRhs ( )
51+ result = this .getOptionArgument ( 0 , flag )
8552 }
8653
8754 override predicate isSecure ( ) {
@@ -90,8 +57,8 @@ module Cookie {
9057 not getCookieFlagValue ( secureFlag ( ) ) .mayHaveBooleanValue ( false )
9158 }
9259
93- override predicate isAuthNotHttpOnly ( ) {
94- not isHttpOnly ( ) // It is a session cookie, likely auth sensitive
60+ override predicate isSensitive ( ) {
61+ any ( ) // It is a session cookie, likely auth sensitive
9562 }
9663
9764 override predicate isHttpOnly ( ) {
@@ -105,13 +72,9 @@ module Cookie {
10572 * A cookie set using the `express` module `express-session` (https://github.com/expressjs/session).
10673 */
10774 class InsecureExpressSessionCookie extends ExpressLibraries:: ExpressSession:: MiddlewareInstance ,
108- Cookie {
109- override string getKind ( ) { result = "express-session" }
110-
111- override DataFlow:: SourceNode getCookieOptionsArgument ( ) { result = this .getOption ( "cookie" ) }
112-
75+ CookieWrite {
11376 private DataFlow:: Node getCookieFlagValue ( string flag ) {
114- result = this .getCookieOptionsArgument ( ) .getAPropertyWrite ( flag ) .getRhs ( )
77+ result = this .getOption ( "cookie" ) . getALocalSource ( ) .getAPropertyWrite ( flag ) .getRhs ( )
11578 }
11679
11780 override predicate isSecure ( ) {
@@ -122,8 +85,8 @@ module Cookie {
12285 getCookieFlagValue ( secureFlag ( ) ) .mayHaveStringValue ( "auto" )
12386 }
12487
125- override predicate isAuthNotHttpOnly ( ) {
126- not isHttpOnly ( ) // It is a session cookie, likely auth sensitive
88+ override predicate isSensitive ( ) {
89+ any ( ) // It is a session cookie, likely auth sensitive
12790 }
12891
12992 override predicate isHttpOnly ( ) {
@@ -137,17 +100,11 @@ module Cookie {
137100 /**
138101 * A cookie set using `response.cookie` from `express` module (https://expressjs.com/en/api.html#res.cookie).
139102 */
140- class InsecureExpressCookieResponse extends Cookie , DataFlow:: MethodCallNode {
103+ class InsecureExpressCookieResponse extends CookieWrite , DataFlow:: MethodCallNode {
141104 InsecureExpressCookieResponse ( ) { this .calls ( any ( Express:: ResponseExpr r ) .flow ( ) , "cookie" ) }
142105
143- override string getKind ( ) { result = "response.cookie" }
144-
145- override DataFlow:: SourceNode getCookieOptionsArgument ( ) {
146- result = this .getLastArgument ( ) .getALocalSource ( )
147- }
148-
149106 private DataFlow:: Node getCookieFlagValue ( string flag ) {
150- result = this .getCookieOptionsArgument ( ) . getAPropertyWrite ( flag ) . getRhs ( )
107+ result = this .getOptionArgument ( this . getNumArgument ( ) - 1 , flag )
151108 }
152109
153110 override predicate isSecure ( ) {
@@ -156,9 +113,12 @@ module Cookie {
156113 getCookieFlagValue ( secureFlag ( ) ) .mayHaveBooleanValue ( true )
157114 }
158115
159- override predicate isAuthNotHttpOnly ( ) {
160- isAuthVariable ( this .getArgument ( 0 ) ) and
161- not isHttpOnly ( )
116+ override predicate isSensitive ( ) {
117+ HeuristicNames:: nameIndicatesSensitiveData ( any ( string s |
118+ this .getArgument ( 0 ) .mayHaveStringValue ( s )
119+ ) , _)
120+ or
121+ this .getArgument ( 0 ) .asExpr ( ) instanceof SensitiveExpr
162122 }
163123
164124 override predicate isHttpOnly ( ) {
@@ -168,101 +128,56 @@ module Cookie {
168128 }
169129 }
170130
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-
191131 /**
192132 * A cookie set using `Set-Cookie` header of an `HTTP` response.
193133 * (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
194134 * In case an array is passed `setHeader("Set-Cookie", [...]` it sets multiple cookies.
195135 * Each array element has its own attributes.
196136 */
197- class InsecureSetCookieHeader extends Cookie {
137+ class InsecureSetCookieHeader extends CookieWrite {
198138 InsecureSetCookieHeader ( ) {
199- this .asExpr ( ) = any ( HTTP:: SetCookieHeader setCookie ) .getHeaderArgument ( )
200- }
201-
202- override string getKind ( ) { result = "set-cookie header" }
203-
204- override DataFlow :: Node getCookieOptionsArgument ( ) {
205- if this . asExpr ( ) instanceof ArrayExpr
206- then result . asExpr ( ) = this . asExpr ( ) . ( ArrayExpr ) . getAnElement ( )
207- else result . asExpr ( ) = this . asExpr ( )
139+ this .asExpr ( ) = any ( HTTP:: SetCookieHeader setCookie ) .getHeaderArgument ( ) and
140+ not this instanceof DataFlow :: ArrayCreationNode
141+ or
142+ this =
143+ any ( HTTP :: SetCookieHeader setCookie )
144+ . getHeaderArgument ( )
145+ . flow ( )
146+ . ( DataFlow :: ArrayCreationNode )
147+ . getAnElement ( )
208148 }
209149
210150 /**
211151 * A cookie is secure if the `secure` flag is specified in the cookie definition.
212152 * The default is `false`.
213153 */
214- override predicate isSecure ( ) { allHaveCookieAttribute ( "secure" ) }
154+ override predicate isSecure ( ) { hasCookieAttribute ( "secure" ) }
215155
216156 /**
217157 * A cookie is httpOnly if the `httpOnly` flag is specified in the cookie definition.
218158 * The default is `false`.
219159 */
220- override predicate isHttpOnly ( ) { allHaveCookieAttribute ( httpOnlyFlag ( ) ) }
160+ override predicate isHttpOnly ( ) { hasCookieAttribute ( httpOnlyFlag ( ) ) }
221161
222162 /**
223- * The predicate holds only if all elements have the specified attribute.
163+ * The predicate holds only if any element have the specified attribute.
224164 */
225165 bindingset [ attribute]
226- private predicate allHaveCookieAttribute ( string attribute ) {
227- forall ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
228- exists ( string s |
229- n .mayHaveStringValue ( s ) and
230- hasCookieAttribute ( s , attribute )
231- )
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- )
166+ private predicate hasCookieAttribute ( string attribute ) {
167+ exists ( string s |
168+ this .mayHaveStringValue ( s ) and
169+ hasCookieAttribute ( s , attribute )
240170 )
241171 }
242172
243173 /**
244- * The predicate holds only if any element has a sensitive name and
245- * doesn't have the `httpOnly` flag.
174+ * The predicate holds only if any element has a sensitive name.
246175 */
247- override predicate isAuthNotHttpOnly ( ) {
248- exists ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
249- exists ( string s |
250- n .mayHaveStringValue ( s ) and
251- (
252- not hasCookieAttribute ( s , httpOnlyFlag ( ) ) and
253- regexpMatchAuth ( getCookieName ( s ) )
254- )
255- )
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 ) )
265- )
176+ override predicate isSensitive ( ) {
177+ HeuristicNames:: nameIndicatesSensitiveData ( getCookieName ( [
178+ any ( string s | this .mayHaveStringValue ( s ) ) ,
179+ this .( StringOps:: ConcatenationRoot ) .getConstantStringParts ( )
180+ ] ) , _)
266181 }
267182
268183 /**
@@ -271,7 +186,9 @@ module Cookie {
271186 * `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
272187 */
273188 bindingset [ s]
274- private string getCookieName ( string s ) { result = s .regexpCapture ( "\\s*([^=\\s]*)\\s*=.*" , 1 ) }
189+ private string getCookieName ( string s ) {
190+ result = s .regexpCapture ( "\\s*\\b([^=\\s]*)\\b\\s*=.*" , 1 )
191+ }
275192
276193 /**
277194 * Holds if the `Set-Cookie` header value contains the specified attribute
@@ -284,14 +201,14 @@ module Cookie {
284201 */
285202 bindingset [ s, attribute]
286203 private predicate hasCookieAttribute ( string s , string attribute ) {
287- s .regexpMatch ( "(?i).*;\\s*" + attribute + "\\s*;?.*$" )
204+ s .regexpMatch ( "(?i).*;\\s*" + attribute + "\\b\\ s*;?.*$" )
288205 }
289206 }
290207
291208 /**
292209 * A cookie set using `js-cookie` library (https://github.com/js-cookie/js-cookie).
293210 */
294- class InsecureJsCookie extends Cookie {
211+ class InsecureJsCookie extends CookieWrite {
295212 InsecureJsCookie ( ) {
296213 this =
297214 [
@@ -301,9 +218,7 @@ module Cookie {
301218 ] .getAMemberCall ( "set" )
302219 }
303220
304- override string getKind ( ) { result = "js-cookie" }
305-
306- override DataFlow:: SourceNode getCookieOptionsArgument ( ) {
221+ DataFlow:: SourceNode getCookieOptionsArgument ( ) {
307222 result = this .( DataFlow:: CallNode ) .getAnArgument ( ) .getALocalSource ( )
308223 }
309224
@@ -316,7 +231,7 @@ module Cookie {
316231 getCookieFlagValue ( secureFlag ( ) ) .mayHaveBooleanValue ( true )
317232 }
318233
319- override predicate isAuthNotHttpOnly ( ) { none ( ) }
234+ override predicate isSensitive ( ) { none ( ) }
320235
321236 override predicate isHttpOnly ( ) { none ( ) } // js-cookie is browser side library and doesn't support HttpOnly
322237 }
0 commit comments