11/**
22 * @name Sensitive cookies without the HttpOnly response header set
33 * @description Sensitive cookies without the 'HttpOnly' flag set leaves session cookies vulnerable to
4- * an XSS attack. This query checks whether 'HttpOnly' is not set in a Java Cookie object
5- * or the 'Set-Cookie' HTTP header.
4+ * an XSS attack.
65 * @kind path-problem
76 * @id java/sensitive-cookie-not-httponly
87 * @tags security
98 * external/cwe/cwe-1004
109 */
1110
11+ /*
12+ * Sketch of the structure of this query: we track cookie names that appear to be sensitive
13+ * (e.g. `session` or `token`) to a `ServletResponse.addHeader(...)` or `.addCookie(...)`
14+ * method that does not set the `httpOnly` flag. Subsidiary configurations
15+ * `MatchesHttpOnlyConfiguration` and `SetHttpOnlyInCookieConfiguration` are used to establish
16+ * when the `httpOnly` flag is likely to have been set, before configuration
17+ * `MissingHttpOnlyConfiguration` establishes that a non-`httpOnly` cookie has a sensitive-seeming name.
18+ */
19+
1220import java
1321import semmle.code.java.dataflow.FlowSteps
1422import semmle.code.java.frameworks.Servlets
@@ -34,6 +42,11 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
3442 isSensitiveCookieNameExpr ( expr .( AddExpr ) .getAnOperand ( ) )
3543}
3644
45+ /** A sensitive cookie name. */
46+ class SensitiveCookieNameExpr extends Expr {
47+ SensitiveCookieNameExpr ( ) { isSensitiveCookieNameExpr ( this ) }
48+ }
49+
3750/** A method call that sets a `Set-Cookie` header. */
3851class SetCookieMethodAccess extends MethodAccess {
3952 SetCookieMethodAccess ( ) {
@@ -70,36 +83,55 @@ class CookieClass extends RefType {
7083}
7184
7285/** Holds if the `expr` is `true` or a variable that is ever assigned `true`. */
86+ // This could be a very large result set if computed out of context
87+ pragma [ inline]
7388predicate mayBeBooleanTrue ( Expr expr ) {
74- expr .( CompileTimeConstantExpr ) .getBooleanValue ( ) = true or
75- expr .( VarAccess ) .getVariable ( ) .getAnAssignedValue ( ) .( CompileTimeConstantExpr ) .getBooleanValue ( ) =
76- true
89+ expr .getType ( ) instanceof BooleanType and
90+ not expr .( CompileTimeConstantExpr ) .getBooleanValue ( ) = false
7791}
7892
79- /** Holds if the method call sets the `HttpOnly` flag. */
93+ /** Holds if the method call may set the `HttpOnly` flag. */
8094predicate setsCookieHttpOnly ( MethodAccess ma ) {
8195 ma .getMethod ( ) .getName ( ) = "setHttpOnly" and
82- (
83- ma .getArgument ( 0 ) .( CompileTimeConstantExpr ) .getBooleanValue ( ) = true
84- or
85- // any use of setHttpOnly(x) where x isn't false is probably safe
86- not exists ( VarAccess va |
87- ma .getArgument ( 0 ) .( VarAccess ) .getVariable ( ) .getAnAccess ( ) = va and
88- va .getVariable ( ) .getAnAssignedValue ( ) .( CompileTimeConstantExpr ) .getBooleanValue ( ) = false
96+ // any use of setHttpOnly(x) where x isn't false is probably safe
97+ mayBeBooleanTrue ( ma .getArgument ( 0 ) )
98+ }
99+
100+ /** Holds if `ma` removes a cookie. */
101+ predicate removesCookie ( MethodAccess ma ) {
102+ ma .getMethod ( ) .getName ( ) = "setMaxAge" and
103+ ma .getArgument ( 0 ) .( IntegerLiteral ) .getIntValue ( ) = 0
104+ }
105+
106+ /**
107+ * Holds if the MethodAccess `ma` is a test method call indicated by:
108+ * a) in a test directory such as `src/test/java`
109+ * b) in a test package whose name has the word `test`
110+ * c) in a test class whose name has the word `test`
111+ * d) in a test class implementing a test framework such as JUnit or TestNG
112+ */
113+ predicate isTestMethod ( MethodAccess ma ) {
114+ exists ( Method m |
115+ m = ma .getEnclosingCallable ( ) and
116+ (
117+ m .getDeclaringType ( ) .getName ( ) .toLowerCase ( ) .matches ( "%test%" ) or // Simple check to exclude test classes to reduce FPs
118+ m .getDeclaringType ( ) .getPackage ( ) .getName ( ) .toLowerCase ( ) .matches ( "%test%" ) or // Simple check to exclude classes in test packages to reduce FPs
119+ exists ( m .getLocation ( ) .getFile ( ) .getAbsolutePath ( ) .indexOf ( "/src/test/java" ) ) or // Match test directory structure of build tools like maven
120+ m instanceof TestMethod // Test method of a test case implementing a test framework such as JUnit or TestNG
89121 )
90122 )
91123}
92124
93125/**
94- * A taint configuration tracking flow of a method or a wrapper method that sets
95- * the `HttpOnly` flag .
126+ * A taint configuration tracking flow of a method or a wrapper method that sets the `HttpOnly`
127+ * flag, or one that removes a cookie, to a `ServletResponse.addCookie` call .
96128 */
97129class SetHttpOnlyInCookieConfiguration extends TaintTracking2:: Configuration {
98130 SetHttpOnlyInCookieConfiguration ( ) { this = "SetHttpOnlyInCookieConfiguration" }
99131
100132 override predicate isSource ( DataFlow:: Node source ) {
101133 source .asExpr ( ) =
102- any ( MethodAccess ma | setsCookieHttpOnly ( ma ) or removeCookie ( ma ) ) .getQualifier ( )
134+ any ( MethodAccess ma | setsCookieHttpOnly ( ma ) or removesCookie ( ma ) ) .getQualifier ( )
103135 }
104136
105137 override predicate isSink ( DataFlow:: Node sink ) {
@@ -108,18 +140,10 @@ class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
108140 }
109141}
110142
111- /** Holds if `ma` removes a cookie. */
112- predicate removeCookie ( MethodAccess ma ) {
113- ma .getMethod ( ) .getName ( ) = "setMaxAge" and
114- ma .getArgument ( 0 ) .( IntegerLiteral ) .getIntValue ( ) = 0
115- }
116-
117- /** A sensitive cookie name. */
118- class SensitiveCookieNameExpr extends Expr {
119- SensitiveCookieNameExpr ( ) { isSensitiveCookieNameExpr ( this ) }
120- }
121-
122- /** A cookie that is added to an HTTP response, used as a sink in `MissingHttpOnlyConfiguration`. */
143+ /**
144+ * A cookie that is added to an HTTP response and which doesn't have `httpOnly` set, used as a sink
145+ * in `MissingHttpOnlyConfiguration`.
146+ */
123147class CookieResponseSink extends DataFlow:: ExprNode {
124148 CookieResponseSink ( ) {
125149 exists ( MethodAccess ma |
@@ -137,11 +161,8 @@ class CookieResponseSink extends DataFlow::ExprNode {
137161 }
138162}
139163
140- /**
141- * Holds if `ClassInstanceExpr` cie is an invocation of a JAX-RS `NewCookie` constructor
142- * that sets `HttpOnly` to true.
143- */
144- predicate setHttpOnlyInNewCookie ( ClassInstanceExpr cie ) {
164+ /** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
165+ predicate setsHttpOnlyInNewCookie ( ClassInstanceExpr cie ) {
145166 cie .getConstructedType ( ) .hasQualifiedName ( [ "javax.ws.rs.core" , "jakarta.ws.rs.core" ] , "NewCookie" ) and
146167 (
147168 cie .getNumArgument ( ) = 6 and
@@ -156,42 +177,6 @@ predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
156177 )
157178}
158179
159- /** The cookie constructor. */
160- class CookieTaintPreservingConstructor extends Constructor , TaintPreservingCallable {
161- CookieTaintPreservingConstructor ( ) { this .getDeclaringType ( ) instanceof CookieClass }
162-
163- override predicate returnsTaintFrom ( int arg ) { arg = 0 }
164- }
165-
166- /** The method call `toString` to get a stringified cookie representation. */
167- class CookieToString extends TaintPreservingCallable {
168- CookieToString ( ) {
169- this .getDeclaringType ( ) instanceof CookieClass and
170- this .hasName ( "toString" )
171- }
172-
173- override predicate returnsTaintFrom ( int arg ) { arg = - 1 }
174- }
175-
176- /**
177- * Holds if the MethodAccess `ma` is a test method call indicated by:
178- * a) in a test directory such as `src/test/java`
179- * b) in a test package whose name has the word `test`
180- * c) in a test class whose name has the word `test`
181- * d) in a test class implementing a test framework such as JUnit or TestNG
182- */
183- predicate isTestMethod ( MethodAccess ma ) {
184- exists ( Method m |
185- m = ma .getEnclosingCallable ( ) and
186- (
187- m .getDeclaringType ( ) .getName ( ) .toLowerCase ( ) .matches ( "%test%" ) or // Simple check to exclude test classes to reduce FPs
188- m .getDeclaringType ( ) .getPackage ( ) .getName ( ) .toLowerCase ( ) .matches ( "%test%" ) or // Simple check to exclude classes in test packages to reduce FPs
189- exists ( m .getLocation ( ) .getFile ( ) .getAbsolutePath ( ) .indexOf ( "/src/test/java" ) ) or // Match test directory structure of build tools like maven
190- m instanceof TestMethod // Test method of a test case implementing a test framework such as JUnit or TestNG
191- )
192- )
193- }
194-
195180/**
196181 * A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
197182 * set to its HTTP response.
@@ -206,8 +191,27 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
206191 override predicate isSink ( DataFlow:: Node sink ) { sink instanceof CookieResponseSink }
207192
208193 override predicate isSanitizer ( DataFlow:: Node node ) {
209- // new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)
210- setHttpOnlyInNewCookie ( node .asExpr ( ) )
194+ // JAX-RS's `new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)` and similar
195+ setsHttpOnlyInNewCookie ( node .asExpr ( ) )
196+ }
197+
198+ override predicate isAdditionalTaintStep ( DataFlow:: Node pred , DataFlow:: Node succ ) {
199+ exists (
200+ ConstructorCall cc // new Cookie(...)
201+ |
202+ cc .getConstructedType ( ) instanceof CookieClass and
203+ pred .asExpr ( ) = cc .getAnArgument ( ) and
204+ succ .asExpr ( ) = cc
205+ )
206+ or
207+ exists (
208+ MethodAccess ma // cookie.toString()
209+ |
210+ ma .getMethod ( ) .getName ( ) = "toString" and
211+ ma .getQualifier ( ) .getType ( ) instanceof CookieClass and
212+ pred .asExpr ( ) = ma .getQualifier ( ) and
213+ succ .asExpr ( ) = ma
214+ )
211215 }
212216}
213217
0 commit comments