@@ -59,7 +59,6 @@ module CookieWrites {
59
59
/**
60
60
* A model of the `js-cookie` library (https://github.com/js-cookie/js-cookie).
61
61
*/
62
- // TODO: Writes to document.cookie.
63
62
private module JsCookie {
64
63
/**
65
64
* Gets a function call that invokes method `name` of the `js-cookie` library.
@@ -118,13 +117,27 @@ private module BrowserCookies {
118
117
}
119
118
}
120
119
121
- class WriteAccess extends PersistentWriteAccess , DataFlow:: CallNode {
122
- // TODO: CookieWrite
120
+ class WriteAccess extends PersistentWriteAccess , DataFlow:: CallNode ,
121
+ CookieWrites :: ClientSideCookieWrite {
123
122
WriteAccess ( ) { this = libMemberCall ( "set" ) }
124
123
125
124
string getKey ( ) { getArgument ( 0 ) .mayHaveStringValue ( result ) }
126
125
127
126
override DataFlow:: Node getValue ( ) { result = getArgument ( 1 ) }
127
+
128
+ override predicate isSecure ( ) {
129
+ // A cookie is secure if there are cookie options with the `secure` flag set to `true`.
130
+ this .getOptionArgument ( 2 , CookieWrites:: secure ( ) ) .mayHaveBooleanValue ( true )
131
+ or
132
+ // or, an explicit default has been set
133
+ exists ( DataFlow:: moduleMember ( "browser-cookies" , "defaults" ) .getAPropertyWrite ( "secure" ) )
134
+ }
135
+
136
+ override predicate isSensitive ( ) {
137
+ HeuristicNames:: nameIndicatesSensitiveData ( any ( string s |
138
+ this .getArgument ( 0 ) .mayHaveStringValue ( s )
139
+ ) , _)
140
+ }
128
141
}
129
142
}
130
143
@@ -147,13 +160,24 @@ private module LibCookie {
147
160
override PersistentWriteAccess getAWrite ( ) { key = result .( WriteAccess ) .getKey ( ) }
148
161
}
149
162
150
- class WriteAccess extends PersistentWriteAccess , DataFlow:: CallNode {
151
- // TODO: CookieWrite
163
+ class WriteAccess extends PersistentWriteAccess , DataFlow:: CallNode ,
164
+ CookieWrites :: ClientSideCookieWrite {
152
165
WriteAccess ( ) { this = libMemberCall ( "serialize" ) }
153
166
154
167
string getKey ( ) { getArgument ( 0 ) .mayHaveStringValue ( result ) }
155
168
156
169
override DataFlow:: Node getValue ( ) { result = getArgument ( 1 ) }
170
+
171
+ override predicate isSecure ( ) {
172
+ // A cookie is secure if there are cookie options with the `secure` flag set to `true`.
173
+ this .getOptionArgument ( 2 , CookieWrites:: secure ( ) ) .mayHaveBooleanValue ( true )
174
+ }
175
+
176
+ override predicate isSensitive ( ) {
177
+ HeuristicNames:: nameIndicatesSensitiveData ( any ( string s |
178
+ this .getArgument ( 0 ) .mayHaveStringValue ( s )
179
+ ) , _)
180
+ }
157
181
}
158
182
}
159
183
@@ -286,28 +310,56 @@ private class HTTPCookieWrite extends CookieWrites::CookieWrite {
286
310
override predicate isSensitive ( ) {
287
311
HeuristicNames:: nameIndicatesSensitiveData ( getCookieName ( header ) , _)
288
312
}
313
+ }
289
314
290
- /**
291
- * Gets cookie name from a `Set-Cookie` header value.
292
- * The header value always starts with `<cookie-name>=<cookie-value>` optionally followed by attributes:
293
- * `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
294
- */
295
- bindingset [ s]
296
- private string getCookieName ( string s ) {
297
- result = s .regexpCapture ( "\\s*\\b([^=\\s]*)\\b\\s*=.*" , 1 )
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*;?.*$" )
337
+ }
338
+
339
+ /**
340
+ * A write to `document.cookie`.
341
+ */
342
+ private class DocumentCookieWrite extends CookieWrites:: ClientSideCookieWrite {
343
+ string cookie ;
344
+
345
+ 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
+ )
298
354
}
299
355
300
- /**
301
- * Holds if the `Set-Cookie` header value contains the specified attribute
302
- * 1. The attribute is case insensitive
303
- * 2. It always starts with a pair `<cookie-name>=<cookie-value>`.
304
- * If the attribute is present there must be `;` after the pair.
305
- * Other attributes like `Domain=`, `Path=`, etc. may come after the pair:
306
- * `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
307
- * See `https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie`
308
- */
309
- bindingset [ s, attribute]
310
- private predicate hasCookieAttribute ( string s , string attribute ) {
311
- s .regexpMatch ( "(?i).*;\\s*" + attribute + "\\b\\s*;?.*$" )
356
+ override predicate isSecure ( ) {
357
+ // A cookie is secure if the `secure` flag is specified in the cookie definition.
358
+ // The default is `false`.
359
+ hasCookieAttribute ( cookie , CookieWrites:: secure ( ) )
360
+ }
361
+
362
+ override predicate isSensitive ( ) {
363
+ HeuristicNames:: nameIndicatesSensitiveData ( getCookieName ( cookie ) , _)
312
364
}
313
365
}
0 commit comments