Skip to content

Commit b366ffa

Browse files
committed
Revamp source of the query
1 parent 95d1994 commit b366ffa

File tree

5 files changed

+82
-143
lines changed

5 files changed

+82
-143
lines changed

java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<qhelp>
33

44
<overview>
5-
<p>Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The <code>HttpOnly</code> flag directs compatible browsers to prevent client-side script from accessing cookies. Including the HttpOnly flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.</p>
5+
<p>Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The <code>HttpOnly</code> flag directs compatible browsers to prevent client-side script from accessing cookies. Including the <code>HttpOnly</code> flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.</p>
66
</overview>
77

88
<recommendation>

java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import java
1111
import semmle.code.java.frameworks.Servlets
12-
import semmle.code.java.dataflow.FlowSources
1312
import semmle.code.java.dataflow.TaintTracking
1413
import 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. */
2019
predicate 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. */
2928
predicate 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. */
4645
class 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+
*/
9091
predicate 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+
*/
104108
predicate 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+
*/
149153
class 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
|
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
edges
2-
| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
3-
| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
2+
| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
3+
| SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
44
nodes
5-
| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | semmle.label | "jwt_token" : String |
5+
| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | semmle.label | new Cookie(...) : Cookie |
66
| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | semmle.label | jwtCookie |
77
| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | semmle.label | ... + ... |
8+
| SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | semmle.label | new NewCookie(...) : NewCookie |
89
| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | semmle.label | toString(...) |
9-
| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | semmle.label | "session-access-key" : String |
1010
#select
11-
| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" | This sensitive cookie |
11+
| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) | This sensitive cookie |
1212
| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | This sensitive cookie |
13-
| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" | This sensitive cookie |
13+
| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) | This sensitive cookie |

java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,16 @@
5151
* @since 1.0
5252
*/
5353
public class Cookie {
54+
5455
/**
5556
* Cookies using the default version correspond to RFC 2109.
5657
*/
5758
public static final int DEFAULT_VERSION = 1;
59+
private final String name;
60+
private final String value;
61+
private final int version;
62+
private final String path;
63+
private final String domain;
5864

5965
/**
6066
* Create a new instance.
@@ -68,6 +74,11 @@ public class Cookie {
6874
*/
6975
public Cookie(final String name, final String value, final String path, final String domain, final int version)
7076
throws IllegalArgumentException {
77+
this.name = name;
78+
this.value = value;
79+
this.version = version;
80+
this.domain = domain;
81+
this.path = path;
7182
}
7283

7384
/**
@@ -81,6 +92,7 @@ public Cookie(final String name, final String value, final String path, final St
8192
*/
8293
public Cookie(final String name, final String value, final String path, final String domain)
8394
throws IllegalArgumentException {
95+
this(name, value, path, domain, DEFAULT_VERSION);
8496
}
8597

8698
/**
@@ -92,6 +104,7 @@ public Cookie(final String name, final String value, final String path, final St
92104
*/
93105
public Cookie(final String name, final String value)
94106
throws IllegalArgumentException {
107+
this(name, value, null, null);
95108
}
96109

97110
/**
@@ -112,7 +125,7 @@ public static Cookie valueOf(final String value) {
112125
* @return the cookie name.
113126
*/
114127
public String getName() {
115-
return null;
128+
return name;
116129
}
117130

118131
/**
@@ -121,7 +134,7 @@ public String getName() {
121134
* @return the cookie value.
122135
*/
123136
public String getValue() {
124-
return null;
137+
return value;
125138
}
126139

127140
/**
@@ -130,7 +143,7 @@ public String getValue() {
130143
* @return the cookie version.
131144
*/
132145
public int getVersion() {
133-
return -1;
146+
return version;
134147
}
135148

136149
/**
@@ -139,7 +152,7 @@ public int getVersion() {
139152
* @return the cookie domain.
140153
*/
141154
public String getDomain() {
142-
return null;
155+
return domain;
143156
}
144157

145158
/**
@@ -148,40 +161,6 @@ public String getDomain() {
148161
* @return the cookie path.
149162
*/
150163
public String getPath() {
151-
return null;
152-
}
153-
154-
/**
155-
* Convert the cookie to a string suitable for use as the value of the
156-
* corresponding HTTP header.
157-
*
158-
* @return a stringified cookie.
159-
*/
160-
@Override
161-
public String toString() {
162-
return null;
163-
}
164-
165-
/**
166-
* Generate a hash code by hashing all of the cookies properties.
167-
*
168-
* @return the cookie hash code.
169-
*/
170-
@Override
171-
public int hashCode() {
172-
return -1;
173-
}
174-
175-
/**
176-
* Compare for equality.
177-
*
178-
* @param obj the object to compare to.
179-
* @return {@code true}, if the object is a {@code Cookie} with the same
180-
* value for all properties, {@code false} otherwise.
181-
*/
182-
@SuppressWarnings({"StringEquality", "RedundantIfStatement"})
183-
@Override
184-
public boolean equals(final Object obj) {
185-
return true;
164+
return path;
186165
}
187-
}
166+
}

java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -320,40 +320,4 @@ public boolean isHttpOnly() {
320320
public Cookie toCookie() {
321321
return null;
322322
}
323-
324-
/**
325-
* Convert the cookie to a string suitable for use as the value of the
326-
* corresponding HTTP header.
327-
*
328-
* @return a stringified cookie.
329-
*/
330-
@Override
331-
public String toString() {
332-
return null;
333-
}
334-
335-
/**
336-
* Generate a hash code by hashing all of the properties.
337-
*
338-
* @return the hash code.
339-
*/
340-
@Override
341-
public int hashCode() {
342-
return -1;
343-
}
344-
345-
/**
346-
* Compare for equality. Use {@link #toCookie()} to compare a
347-
* {@code NewCookie} to a {@code Cookie} considering only the common
348-
* properties.
349-
*
350-
* @param obj the object to compare to
351-
* @return true if the object is a {@code NewCookie} with the same value for
352-
* all properties, false otherwise.
353-
*/
354-
@SuppressWarnings({"StringEquality", "RedundantIfStatement"})
355-
@Override
356-
public boolean equals(Object obj) {
357-
return true;
358-
}
359323
}

0 commit comments

Comments
 (0)