99
1010import java
1111import semmle.code.java.frameworks.Servlets
12- import semmle.code.java.dataflow.FlowSources
1312import semmle.code.java.dataflow.TaintTracking
1413import DataFlow:: PathGraph
1514
@@ -18,16 +17,16 @@ string getSensitiveCookieNameRegex() { result = "(?i).*(auth|session|token|key|c
1817
1918/** Holds if a string is concatenated with the name of a sensitive cookie. */
2019predicate isSensitiveCookieNameExpr ( Expr expr ) {
21- expr .( StringLiteral )
22- .getRepresentedString ( )
20+ expr .( CompileTimeConstantExpr )
21+ .getStringValue ( )
2322 .toLowerCase ( )
2423 .regexpMatch ( getSensitiveCookieNameRegex ( ) ) or
2524 isSensitiveCookieNameExpr ( expr .( AddExpr ) .getAnOperand ( ) )
2625}
2726
2827/** Holds if a string is concatenated with the `HttpOnly` flag. */
2928predicate hasHttpOnlyExpr ( Expr expr ) {
30- expr .( StringLiteral ) . getRepresentedString ( ) .toLowerCase ( ) .matches ( "%httponly%" ) or
29+ expr .( CompileTimeConstantExpr ) . getStringValue ( ) .toLowerCase ( ) .matches ( "%httponly%" ) or
3130 hasHttpOnlyExpr ( expr .( AddExpr ) .getAnOperand ( ) )
3231}
3332
@@ -38,37 +37,34 @@ class SetCookieMethodAccess extends MethodAccess {
3837 this .getMethod ( ) instanceof ResponseAddHeaderMethod or
3938 this .getMethod ( ) instanceof ResponseSetHeaderMethod
4039 ) and
41- this .getArgument ( 0 ) .( StringLiteral ) . getRepresentedString ( ) .toLowerCase ( ) = "set-cookie"
40+ this .getArgument ( 0 ) .( CompileTimeConstantExpr ) . getStringValue ( ) .toLowerCase ( ) = "set-cookie"
4241 }
4342}
4443
4544/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
4645class SensitiveCookieNameExpr extends Expr {
4746 SensitiveCookieNameExpr ( ) {
48- isSensitiveCookieNameExpr ( this ) and
49- (
50- exists (
51- ClassInstanceExpr cie // new Cookie("jwt_token", token)
52- |
53- (
54- cie .getConstructor ( ) .getDeclaringType ( ) .hasQualifiedName ( "javax.servlet.http" , "Cookie" ) or
55- cie .getConstructor ( )
56- .getDeclaringType ( )
57- .getASupertype * ( )
58- .hasQualifiedName ( "javax.ws.rs.core" , "Cookie" ) or
59- cie .getConstructor ( )
60- .getDeclaringType ( )
61- .getASupertype * ( )
62- .hasQualifiedName ( "jakarta.ws.rs.core" , "Cookie" )
63- ) and
64- DataFlow:: localExprFlow ( this , cie .getArgument ( 0 ) )
65- )
66- or
67- exists (
68- SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
69- |
70- DataFlow:: localExprFlow ( this , ma .getArgument ( 1 ) )
71- )
47+ exists (
48+ ClassInstanceExpr cie , Expr e // new Cookie("jwt_token", token)
49+ |
50+ (
51+ cie .getConstructor ( ) .getDeclaringType ( ) .hasQualifiedName ( "javax.servlet.http" , "Cookie" ) or
52+ cie .getConstructor ( )
53+ .getDeclaringType ( )
54+ .getASupertype * ( )
55+ .hasQualifiedName ( [ "javax.ws.rs.core" , "jakarta.ws.rs.core" ] , "Cookie" )
56+ ) and
57+ this = cie and
58+ isSensitiveCookieNameExpr ( e ) and
59+ DataFlow:: localExprFlow ( e , cie .getArgument ( 0 ) )
60+ )
61+ or
62+ exists (
63+ SetCookieMethodAccess ma , Expr e // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
64+ |
65+ this = ma .getArgument ( 1 ) and
66+ isSensitiveCookieNameExpr ( e ) and
67+ DataFlow:: localExprFlow ( e , ma .getArgument ( 1 ) )
7268 )
7369 }
7470}
@@ -78,15 +74,20 @@ class CookieResponseSink extends DataFlow::ExprNode {
7874 CookieResponseSink ( ) {
7975 exists ( MethodAccess ma |
8076 (
81- ma .getMethod ( ) instanceof ResponseAddCookieMethod or
82- ma instanceof SetCookieMethodAccess
83- ) and
84- ma .getAnArgument ( ) = this .getExpr ( )
77+ ma .getMethod ( ) instanceof ResponseAddCookieMethod and
78+ this .getExpr ( ) = ma .getArgument ( 0 )
79+ or
80+ ma instanceof SetCookieMethodAccess and
81+ this .getExpr ( ) = ma .getArgument ( 1 )
82+ )
8583 )
8684 }
8785}
8886
89- /** Holds if the `node` is a method call of `setHttpOnly(true)` on a cookie. */
87+ /**
88+ * Holds if `node` is an access to a variable which has `setHttpOnly(true)` called on it and is also
89+ * the first argument to a call to the method `addCookie` of `javax.servlet.http.HttpServletResponse`.
90+ */
9091predicate setHttpOnlyMethodAccess ( DataFlow:: Node node ) {
9192 exists (
9293 MethodAccess addCookie , Variable cookie , MethodAccess m // jwtCookie.setHttpOnly(true)
@@ -100,28 +101,28 @@ predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
100101 )
101102}
102103
103- /** Holds if the `node` is a method call of `Set-Cookie` header with the `HttpOnly` flag whose cookie name is sensitive. */
104+ /**
105+ * Holds if `node` is a string that contains `httponly` and which flows to the second argument
106+ * of a method to set a cookie.
107+ */
104108predicate setHttpOnlyInSetCookie ( DataFlow:: Node node ) {
105109 exists ( SetCookieMethodAccess sa |
106110 hasHttpOnlyExpr ( node .asExpr ( ) ) and
107111 DataFlow:: localExprFlow ( node .asExpr ( ) , sa .getArgument ( 1 ) )
108112 )
109113}
110114
111- /** Holds if the `node` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
112- predicate setHttpOnlyInNewCookie ( DataFlow:: Node node ) {
113- exists ( ClassInstanceExpr cie |
114- cie .getConstructor ( ) .getDeclaringType ( ) .hasName ( "NewCookie" ) and
115- DataFlow:: localExprFlow ( node .asExpr ( ) , cie .getArgument ( 0 ) ) and
116- (
117- cie .getNumArgument ( ) = 6 and cie .getArgument ( 5 ) .( BooleanLiteral ) .getBooleanValue ( ) = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
118- or
119- cie .getNumArgument ( ) = 8 and
120- cie .getArgument ( 6 ) .getType ( ) instanceof BooleanType and
121- cie .getArgument ( 7 ) .( BooleanLiteral ) .getBooleanValue ( ) = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
122- or
123- cie .getNumArgument ( ) = 10 and cie .getArgument ( 9 ) .( BooleanLiteral ) .getBooleanValue ( ) = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
124- )
115+ /** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
116+ predicate setHttpOnlyInNewCookie ( ClassInstanceExpr cie ) {
117+ cie .getConstructedType ( ) .hasQualifiedName ( [ "javax.ws.rs.core" , "jakarta.ws.rs.core" ] , "NewCookie" ) and
118+ (
119+ cie .getNumArgument ( ) = 6 and cie .getArgument ( 5 ) .( BooleanLiteral ) .getBooleanValue ( ) = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
120+ or
121+ cie .getNumArgument ( ) = 8 and
122+ cie .getArgument ( 6 ) .getType ( ) instanceof BooleanType and
123+ cie .getArgument ( 7 ) .( BooleanLiteral ) .getBooleanValue ( ) = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
124+ or
125+ cie .getNumArgument ( ) = 10 and cie .getArgument ( 9 ) .( BooleanLiteral ) .getBooleanValue ( ) = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
125126 )
126127}
127128
@@ -145,7 +146,10 @@ predicate isTestMethod(DataFlow::Node node) {
145146 )
146147}
147148
148- /** A taint configuration tracking flow from a sensitive cookie without HttpOnly flag set to its HTTP response. */
149+ /**
150+ * A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
151+ * set to its HTTP response.
152+ */
149153class MissingHttpOnlyConfiguration extends TaintTracking:: Configuration {
150154 MissingHttpOnlyConfiguration ( ) { this = "MissingHttpOnlyConfiguration" }
151155
@@ -159,25 +163,17 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
159163 // cookie.setHttpOnly(true)
160164 setHttpOnlyMethodAccess ( node )
161165 or
162- // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
166+ // response.addHeader("Set-Cookie", " token=" +authId + ";HttpOnly;Secure")
163167 setHttpOnlyInSetCookie ( node )
164168 or
165169 // new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)
166- setHttpOnlyInNewCookie ( node )
170+ setHttpOnlyInNewCookie ( node . asExpr ( ) )
167171 or
168172 // Test class or method
169173 isTestMethod ( node )
170174 }
171175
172176 override predicate isAdditionalTaintStep ( DataFlow:: Node pred , DataFlow:: Node succ ) {
173- exists (
174- ClassInstanceExpr cie // `NewCookie` constructor
175- |
176- cie .getAnArgument ( ) = pred .asExpr ( ) and
177- cie = succ .asExpr ( ) and
178- cie .getConstructor ( ) .getDeclaringType ( ) .hasName ( "NewCookie" )
179- )
180- or
181177 exists (
182178 MethodAccess ma // `toString` call on a cookie object
183179 |
0 commit comments