@@ -59,7 +59,6 @@ module CookieWrites {
5959/**
6060 * A model of the `js-cookie` library (https://github.com/js-cookie/js-cookie).
6161 */
62- // TODO: Writes to document.cookie.
6362private module JsCookie {
6463 /**
6564 * Gets a function call that invokes method `name` of the `js-cookie` library.
@@ -118,13 +117,27 @@ private module BrowserCookies {
118117 }
119118 }
120119
121- class WriteAccess extends PersistentWriteAccess , DataFlow:: CallNode {
122- // TODO: CookieWrite
120+ class WriteAccess extends PersistentWriteAccess , DataFlow:: CallNode ,
121+ CookieWrites :: ClientSideCookieWrite {
123122 WriteAccess ( ) { this = libMemberCall ( "set" ) }
124123
125124 string getKey ( ) { getArgument ( 0 ) .mayHaveStringValue ( result ) }
126125
127126 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+ }
128141 }
129142}
130143
@@ -147,13 +160,24 @@ private module LibCookie {
147160 override PersistentWriteAccess getAWrite ( ) { key = result .( WriteAccess ) .getKey ( ) }
148161 }
149162
150- class WriteAccess extends PersistentWriteAccess , DataFlow:: CallNode {
151- // TODO: CookieWrite
163+ class WriteAccess extends PersistentWriteAccess , DataFlow:: CallNode ,
164+ CookieWrites :: ClientSideCookieWrite {
152165 WriteAccess ( ) { this = libMemberCall ( "serialize" ) }
153166
154167 string getKey ( ) { getArgument ( 0 ) .mayHaveStringValue ( result ) }
155168
156169 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+ }
157181 }
158182}
159183
@@ -286,28 +310,56 @@ private class HTTPCookieWrite extends CookieWrites::CookieWrite {
286310 override predicate isSensitive ( ) {
287311 HeuristicNames:: nameIndicatesSensitiveData ( getCookieName ( header ) , _)
288312 }
313+ }
289314
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+ )
298354 }
299355
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 ) , _)
312364 }
313365}
0 commit comments