11/**
22 * Provides classes for reasoning about cookies added to response without the 'secure' flag being set.
33 * A cookie without the 'secure' flag being set can be intercepted and read by a malicious user.
4+ * A cookie without the 'httponly' flag being set can be read by an injected JavaScript
45 */
56
67import javascript
@@ -9,7 +10,12 @@ module Cookie {
910 /**
1011 * `secure` property of the cookie options.
1112 */
12- string flag ( ) { result = "secure" }
13+ string secureFlag ( ) { result = "secure" }
14+
15+ /**
16+ * `httpOnly` property of the cookie options.
17+ */
18+ string httpOnlyFlag ( ) { result = "httpOnly" }
1319
1420 /**
1521 * Abstract class to represent different cases of insecure cookie settings.
@@ -29,6 +35,38 @@ module Cookie {
2935 * Holds if this cookie is secure.
3036 */
3137 abstract predicate isSecure ( ) ;
38+
39+ /**
40+ * Holds if this cookie is HttpOnly.
41+ */
42+ abstract predicate isHttpOnly ( ) ;
43+
44+ /**
45+ * Holds if the cookie is authentication sensitive and lacks HttpOnly.
46+ */
47+ abstract predicate isAuthNotHttpOnly ( ) ;
48+
49+ /**
50+ * Holds if the expression is a variable with a sensitive name.
51+ */
52+ predicate isAuthVariable ( DataFlow:: Node expr ) {
53+ exists ( string val |
54+ (
55+ val = expr .getStringValue ( ) or
56+ val = expr .asExpr ( ) .( VarAccess ) .getName ( )
57+ ) and
58+ regexpMatchAuth ( val )
59+ )
60+ }
61+
62+ /**
63+ * Holds if the string contains sensitive auth keyword, but not antiforgery token.
64+ */
65+ bindingset [ val]
66+ predicate regexpMatchAuth ( string val ) {
67+ val .regexpMatch ( "(?i).*(session|login|token|user|auth|credential).*" ) and
68+ not val .regexpMatch ( "(?i).*(xsrf|csrf|forgery).*" )
69+ }
3270 }
3371
3472 /**
@@ -37,7 +75,7 @@ module Cookie {
3775 class InsecureCookieSession extends ExpressLibraries:: CookieSession:: MiddlewareInstance , Cookie {
3876 override string getKind ( ) { result = "cookie-session" }
3977
40- override DataFlow:: SourceNode getCookieOptionsArgument ( ) { result = this .getOption ( "cookie" ) }
78+ override DataFlow:: SourceNode getCookieOptionsArgument ( ) { result = this .getOptionsArgument ( 0 ) }
4179
4280 private DataFlow:: Node getCookieFlagValue ( string flag ) {
4381 result = this .getCookieOptionsArgument ( ) .getAPropertyWrite ( flag ) .getRhs ( )
@@ -46,7 +84,17 @@ module Cookie {
4684 override predicate isSecure ( ) {
4785 // The flag `secure` is set to `false` by default for HTTP, `true` by default for HTTPS (https://github.com/expressjs/cookie-session#cookie-options).
4886 // A cookie is secure if the `secure` flag is not explicitly set to `false`.
49- not getCookieFlagValue ( flag ( ) ) .mayHaveBooleanValue ( false )
87+ not getCookieFlagValue ( secureFlag ( ) ) .mayHaveBooleanValue ( false )
88+ }
89+
90+ override predicate isAuthNotHttpOnly ( ) {
91+ not isHttpOnly ( ) // It is a session cookie, likely auth sensitive
92+ }
93+
94+ override predicate isHttpOnly ( ) {
95+ // The flag `httpOnly` is set to `true` by default (https://github.com/expressjs/cookie-session#cookie-options).
96+ // A cookie is httpOnly if the `httpOnly` flag is not explicitly set to `false`.
97+ not getCookieFlagValue ( httpOnlyFlag ( ) ) .mayHaveBooleanValue ( false )
5098 }
5199 }
52100
@@ -67,8 +115,19 @@ module Cookie {
67115 // The flag `secure` is not set by default (https://github.com/expressjs/session#Cookieecure).
68116 // The default value for cookie options is { path: '/', httpOnly: true, secure: false, maxAge: null }.
69117 // A cookie is secure if there are the cookie options with the `secure` flag set to `true` or to `auto`.
70- getCookieFlagValue ( flag ( ) ) .mayHaveBooleanValue ( true ) or
71- getCookieFlagValue ( flag ( ) ) .mayHaveStringValue ( "auto" )
118+ getCookieFlagValue ( secureFlag ( ) ) .mayHaveBooleanValue ( true ) or
119+ getCookieFlagValue ( secureFlag ( ) ) .mayHaveStringValue ( "auto" )
120+ }
121+
122+ override predicate isAuthNotHttpOnly ( ) {
123+ not isHttpOnly ( ) // It is a session cookie, likely auth sensitive
124+ }
125+
126+ override predicate isHttpOnly ( ) {
127+ // The flag `httpOnly` is set by default (https://github.com/expressjs/session#Cookieecure).
128+ // The default value for cookie options is { path: '/', httpOnly: true, secure: false, maxAge: null }.
129+ // A cookie is httpOnly if the `httpOnly` flag is not explicitly set to `false`.
130+ not getCookieFlagValue ( httpOnlyFlag ( ) ) .mayHaveBooleanValue ( false )
72131 }
73132 }
74133
@@ -90,7 +149,19 @@ module Cookie {
90149
91150 override predicate isSecure ( ) {
92151 // A cookie is secure if there are cookie options with the `secure` flag set to `true`.
93- getCookieFlagValue ( flag ( ) ) .mayHaveBooleanValue ( true )
152+ // The default is `false`.
153+ getCookieFlagValue ( secureFlag ( ) ) .mayHaveBooleanValue ( true )
154+ }
155+
156+ override predicate isAuthNotHttpOnly ( ) {
157+ isAuthVariable ( this .getArgument ( 0 ) ) and
158+ not isHttpOnly ( )
159+ }
160+
161+ override predicate isHttpOnly ( ) {
162+ // A cookie is httpOnly if there are cookie options with the `httpOnly` flag set to `true`.
163+ // The default is `false`.
164+ getCookieFlagValue ( httpOnlyFlag ( ) ) .mayHaveBooleanValue ( true )
94165 }
95166 }
96167
@@ -105,14 +176,42 @@ module Cookie {
105176 override string getKind ( ) { result = "set-cookie header" }
106177
107178 override DataFlow:: Node getCookieOptionsArgument ( ) {
108- result .asExpr ( ) = this .asExpr ( ) .( ArrayExpr ) .getAnElement ( )
179+ if this .asExpr ( ) instanceof ArrayExpr
180+ then result .asExpr ( ) = this .asExpr ( ) .( ArrayExpr ) .getAnElement ( )
181+ else result .asExpr ( ) = this .asExpr ( )
109182 }
110183
111184 override predicate isSecure ( ) {
112185 // A cookie is secure if the 'secure' flag is specified in the cookie definition.
113- exists ( string s |
114- getCookieOptionsArgument ( ) .mayHaveStringValue ( s ) and
115- s .regexpMatch ( "(.*;)?\\s*secure.*" )
186+ // The default is `false`.
187+ forall ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
188+ exists ( string s |
189+ n .mayHaveStringValue ( s ) and
190+ s .regexpMatch ( "(?i).*;\\s*secure\\s*;?.*$" )
191+ )
192+ )
193+ }
194+
195+ override predicate isAuthNotHttpOnly ( ) {
196+ exists ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
197+ exists ( string s |
198+ n .mayHaveStringValue ( s ) and
199+ (
200+ not s .regexpMatch ( "(?i).*;\\s*httponly\\s*;?.*$" ) and
201+ regexpMatchAuth ( s .regexpCapture ( "\\s*([^=\\s]*)\\s*=.*" , 1 ) )
202+ )
203+ )
204+ )
205+ }
206+
207+ override predicate isHttpOnly ( ) {
208+ // A cookie is httpOnly if the 'httpOnly' flag is specified in the cookie definition.
209+ // The default is `false`.
210+ forall ( DataFlow:: Node n | n = getCookieOptionsArgument ( ) |
211+ exists ( string s |
212+ n .mayHaveStringValue ( s ) and
213+ s .regexpMatch ( "(?i).*;\\s*httponly\\s*;?.*$" )
214+ )
116215 )
117216 }
118217 }
@@ -142,7 +241,11 @@ module Cookie {
142241
143242 override predicate isSecure ( ) {
144243 // A cookie is secure if there are cookie options with the `secure` flag set to `true`.
145- getCookieFlagValue ( flag ( ) ) .mayHaveBooleanValue ( true )
244+ getCookieFlagValue ( secureFlag ( ) ) .mayHaveBooleanValue ( true )
146245 }
246+
247+ override predicate isAuthNotHttpOnly ( ) { none ( ) }
248+
249+ override predicate isHttpOnly ( ) { none ( ) } // js-cookie is browser side library and doesn't support HttpOnly
147250 }
148251}
0 commit comments