@@ -169,7 +169,10 @@ module Cookie {
169
169
}
170
170
171
171
/**
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.
173
176
*/
174
177
class InsecureSetCookieHeader extends Cookie {
175
178
InsecureSetCookieHeader ( ) {
@@ -184,38 +187,67 @@ module Cookie {
184
187
else result .asExpr ( ) = this .asExpr ( )
185
188
}
186
189
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 ) {
190
207
forall ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
191
208
exists ( string s |
192
209
n .mayHaveStringValue ( s ) and
193
- s . regexpMatch ( "(?i).*;\\s*secure\\s*;?.*$" )
210
+ hasCookieAttribute ( s , attribute )
194
211
)
195
212
)
196
213
}
197
214
215
+ /**
216
+ * The predicate holds only if any element has a sensitive name and
217
+ * doesn't have the `httpOnly` flag.
218
+ */
198
219
override predicate isAuthNotHttpOnly ( ) {
199
220
exists ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
200
221
exists ( string s |
201
222
n .mayHaveStringValue ( s ) and
202
223
(
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 ) )
205
226
)
206
227
)
207
228
)
208
229
}
209
230
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*;?.*$" )
219
251
}
220
252
}
221
253
0 commit comments