@@ -169,7 +169,10 @@ module Cookie {
169169 }
170170
171171 /**
172- * A cookie set using `Set-Cookie` header of an `HTTP` response (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
172+ * A cookie set using `Set-Cookie` header of an `HTTP` response.
173+ * (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
174+ * In case an array is passed `setHeader("Set-Cookie", [...]` it sets multiple cookies.
175+ * Each array element has its own attributes.
173176 */
174177 class InsecureSetCookieHeader extends Cookie {
175178 InsecureSetCookieHeader ( ) {
@@ -184,38 +187,67 @@ module Cookie {
184187 else result .asExpr ( ) = this .asExpr ( )
185188 }
186189
187- override predicate isSecure ( ) {
188- // A cookie is secure if the 'secure' flag is specified in the cookie definition.
189- // The default is `false`.
190+ /**
191+ * A cookie is secure if the `secure` flag is specified in the cookie definition.
192+ * The default is `false`.
193+ */
194+ override predicate isSecure ( ) { allHaveCookieAttribute ( "secure" ) }
195+
196+ /**
197+ * A cookie is httpOnly if the `httpOnly` flag is specified in the cookie definition.
198+ * The default is `false`.
199+ */
200+ override predicate isHttpOnly ( ) { allHaveCookieAttribute ( "httponly" ) }
201+
202+ /**
203+ * The predicate holds only if all elements have the specified attribute.
204+ */
205+ bindingset [ attribute]
206+ private predicate allHaveCookieAttribute ( string attribute ) {
190207 forall ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
191208 exists ( string s |
192209 n .mayHaveStringValue ( s ) and
193- s . regexpMatch ( "(?i).*;\\s*secure\\s*;?.*$" )
210+ hasCookieAttribute ( s , attribute )
194211 )
195212 )
196213 }
197214
215+ /**
216+ * The predicate holds only if any element has a sensitive name and
217+ * doesn't have the `httpOnly` flag.
218+ */
198219 override predicate isAuthNotHttpOnly ( ) {
199220 exists ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
200221 exists ( string s |
201222 n .mayHaveStringValue ( s ) and
202223 (
203- not s . regexpMatch ( "(?i).*;\\s* httponly\\s*;?.*$ ") and
204- regexpMatchAuth ( s . regexpCapture ( "\\s*([^=\\s]*)\\s*=.*" , 1 ) )
224+ not hasCookieAttribute ( s , " httponly") and
225+ regexpMatchAuth ( getCookieName ( s ) )
205226 )
206227 )
207228 )
208229 }
209230
210- override predicate isHttpOnly ( ) {
211- // A cookie is httpOnly if the 'httpOnly' flag is specified in the cookie definition.
212- // The default is `false`.
213- forall ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
214- exists ( string s |
215- n .mayHaveStringValue ( s ) and
216- s .regexpMatch ( "(?i).*;\\s*httponly\\s*;?.*$" )
217- )
218- )
231+ /**
232+ * Gets cookie name from a `Set-Cookie` header value.
233+ * The header value always starts with `<cookie-name>=<cookie-value>` optionally followed by attributes:
234+ * `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
235+ */
236+ bindingset [ s]
237+ private string getCookieName ( string s ) { result = s .regexpCapture ( "\\s*([^=\\s]*)\\s*=.*" , 1 ) }
238+
239+ /**
240+ * Holds if the `Set-Cookie` header value contains the specified attribute
241+ * 1. The attribute is case insensitive
242+ * 2. It always starts with a pair `<cookie-name>=<cookie-value>`.
243+ * If the attribute is present there must be `;` after the pair.
244+ * Other attributes like `Domain=`, `Path=`, etc. may come after the pair:
245+ * `<cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly`
246+ * See `https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie`
247+ */
248+ bindingset [ s, attribute]
249+ private predicate hasCookieAttribute ( string s , string attribute ) {
250+ s .regexpMatch ( "(?i).*;\\s*" + attribute + "\\s*;?.*$" )
219251 }
220252 }
221253
0 commit comments