Skip to content

Commit e1cf3d3

Browse files
Update documentation, rename things and add more comments to explain how the implementation works, remove filter for test code (prefer to filter in code scanning ui than in query logic)
1 parent 54aefe0 commit e1cf3d3

File tree

2 files changed

+29
-48
lines changed

2 files changed

+29
-48
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
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 <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>
5+
<p>Cookies without the <code>HttpOnly</code> flag set are accessible to client-side scripts (such as JavaScript) running in the same origin.
6+
In case of a Cross-Site Scripting (XSS) vulnerability, the cookie can be stolen by a malicious script.
7+
If a sensitive cookie does not need to be accessed directly by client-side scripts, the <code>HttpOnly</code> flag should be set.</p>
68
</overview>
79

810
<recommendation>
9-
<p>Use the <code>HttpOnly</code> flag when generating a cookie containing sensitive information to help mitigate the risk of client side script accessing the protected cookie.</p>
11+
<p>Use the <code>HttpOnly</code> flag when generating a cookie containing sensitive information to help mitigate the risk of client-side scripts accessing the protected cookie.</p>
1012
</recommendation>
1113

1214
<example>
@@ -23,5 +25,6 @@
2325
OWASP:
2426
<a href="https://owasp.org/www-community/HttpOnly">HttpOnly</a>
2527
</li>
28+
<li>MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#httponly">Set-Cookie HttpOnly</a>.</li>
2629
</references>
2730
</qhelp>

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

Lines changed: 24 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* Sketch of the structure of this query: we track cookie names that appear to be sensitive
1515
* (e.g. `session` or `token`) to a `ServletResponse.addHeader(...)` or `.addCookie(...)`
1616
* method that does not set the `httpOnly` flag. Subsidiary configurations
17-
* `MatchesHttpOnlyConfiguration` and `SetHttpOnlyInCookieConfiguration` are used to establish
17+
* `MatchesHttpOnlyToRawHeaderConfiguration` and `SetHttpOnlyInCookieConfiguration` are used to establish
1818
* when the `httpOnly` flag is likely to have been set, before configuration
1919
* `MissingHttpOnlyConfiguration` establishes that a non-`httpOnly` cookie has a sensitive-seeming name.
2020
*/
@@ -49,8 +49,8 @@ class SensitiveCookieNameExpr extends Expr {
4949
}
5050

5151
/** A method call that sets a `Set-Cookie` header. */
52-
class SetCookieMethodCall extends MethodCall {
53-
SetCookieMethodCall() {
52+
class SetCookieRawHeaderMethodCall extends MethodCall {
53+
SetCookieRawHeaderMethodCall() {
5454
(
5555
this.getMethod() instanceof ResponseAddHeaderMethod or
5656
this.getMethod() instanceof ResponseSetHeaderMethod
@@ -61,19 +61,19 @@ class SetCookieMethodCall extends MethodCall {
6161

6262
/**
6363
* A taint configuration tracking flow from the text `httponly` to argument 1 of
64-
* `SetCookieMethodCall`.
64+
* `SetCookieRawHeaderMethodCall`.
6565
*/
66-
module MatchesHttpOnlyConfig implements DataFlow::ConfigSig {
66+
module MatchesHttpOnlyToRawHeaderConfig implements DataFlow::ConfigSig {
6767
predicate isSource(DataFlow::Node source) {
6868
source.asExpr().(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
6969
}
7070

7171
predicate isSink(DataFlow::Node sink) {
72-
sink.asExpr() = any(SetCookieMethodCall ma).getArgument(1)
72+
sink.asExpr() = any(SetCookieRawHeaderMethodCall ma).getArgument(1)
7373
}
7474
}
7575

76-
module MatchesHttpOnlyFlow = TaintTracking::Global<MatchesHttpOnlyConfig>;
76+
module MatchesHttpOnlyToRawHeaderFlow = TaintTracking::Global<MatchesHttpOnlyToRawHeaderConfig>;
7777

7878
/** A class descended from `javax.servlet.http.Cookie`. */
7979
class CookieClass extends RefType {
@@ -101,30 +101,11 @@ predicate removesCookie(MethodCall ma) {
101101
ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
102102
}
103103

104-
/**
105-
* Holds if the MethodCall `ma` is a test method call indicated by:
106-
* a) in a test directory such as `src/test/java`
107-
* b) in a test package whose name has the word `test`
108-
* c) in a test class whose name has the word `test`
109-
* d) in a test class implementing a test framework such as JUnit or TestNG
110-
*/
111-
predicate isTestMethod(MethodCall ma) {
112-
exists(Method m |
113-
m = ma.getEnclosingCallable() and
114-
(
115-
m.getDeclaringType().getName().toLowerCase().matches("%test%") or // Simple check to exclude test classes to reduce FPs
116-
m.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") or // Simple check to exclude classes in test packages to reduce FPs
117-
exists(m.getLocation().getFile().getAbsolutePath().indexOf("/src/test/java")) or // Match test directory structure of build tools like maven
118-
m instanceof TestMethod // Test method of a test case implementing a test framework such as JUnit or TestNG
119-
)
120-
)
121-
}
122-
123104
/**
124105
* A taint configuration tracking flow of a method that sets the `HttpOnly` flag,
125106
* or one that removes a cookie, to a `ServletResponse.addCookie` call.
126107
*/
127-
module SetHttpOnlyOrRemovesCookieConfig implements DataFlow::ConfigSig {
108+
module SetHttpOnlyOrRemovesCookieToAddCookieConfig implements DataFlow::ConfigSig {
128109
predicate isSource(DataFlow::Node source) {
129110
source.asExpr() =
130111
any(MethodCall ma | setsCookieHttpOnly(ma) or removesCookie(ma)).getQualifier()
@@ -136,25 +117,25 @@ module SetHttpOnlyOrRemovesCookieConfig implements DataFlow::ConfigSig {
136117
}
137118
}
138119

139-
module SetHttpOnlyOrRemovesCookieFlow = TaintTracking::Global<SetHttpOnlyOrRemovesCookieConfig>;
120+
module SetHttpOnlyOrRemovesCookieToAddCookieFlow =
121+
TaintTracking::Global<SetHttpOnlyOrRemovesCookieToAddCookieConfig>;
140122

141123
/**
142124
* A cookie that is added to an HTTP response and which doesn't have `httpOnly` set, used as a sink
143125
* in `MissingHttpOnlyConfiguration`.
144126
*/
145-
class CookieResponseSink extends DataFlow::ExprNode {
146-
CookieResponseSink() {
127+
class CookieResponseWithoutHttpOnlySink extends DataFlow::ExprNode {
128+
CookieResponseWithoutHttpOnlySink() {
147129
exists(MethodCall ma |
148130
(
149131
ma.getMethod() instanceof ResponseAddCookieMethod and
150132
this.getExpr() = ma.getArgument(0) and
151-
not SetHttpOnlyOrRemovesCookieFlow::flowTo(this)
133+
not SetHttpOnlyOrRemovesCookieToAddCookieFlow::flowTo(this)
152134
or
153-
ma instanceof SetCookieMethodCall and
135+
ma instanceof SetCookieRawHeaderMethodCall and
154136
this.getExpr() = ma.getArgument(1) and
155-
not MatchesHttpOnlyFlow::flowTo(this) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
156-
) and
157-
not isTestMethod(ma) // Test class or method
137+
not MatchesHttpOnlyToRawHeaderFlow::flowTo(this) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
138+
)
158139
)
159140
}
160141
}
@@ -178,14 +159,18 @@ predicate setsHttpOnlyInNewCookie(ClassInstanceExpr cie) {
178159
/**
179160
* A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
180161
* set to its HTTP response.
162+
* Tracks string literals containing sensitive names (`SensitiveNameExpr`), to an `addCookie` call (as a `Cookie` object)
163+
* or an `addHeader` call (as a string) (`CookieResponseWithoutHttpOnly`).
164+
* Passes through `Cookie` constructors and `toString` calls.
181165
*/
182166
module MissingHttpOnlyConfig implements DataFlow::ConfigSig {
183167
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SensitiveCookieNameExpr }
184168

185-
predicate isSink(DataFlow::Node sink) { sink instanceof CookieResponseSink }
169+
predicate isSink(DataFlow::Node sink) { sink instanceof CookieResponseWithoutHttpOnlySink }
186170

187171
predicate isBarrier(DataFlow::Node node) {
188172
// JAX-RS's `new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)` and similar
173+
// Cookie constructors, but barriers to considering the flow of the sensitive name, as httponly flag is set.
189174
setsHttpOnlyInNewCookie(node.asExpr())
190175
}
191176

@@ -211,13 +196,6 @@ module MissingHttpOnlyConfig implements DataFlow::ConfigSig {
211196

212197
module MissingHttpOnlyFlow = TaintTracking::Global<MissingHttpOnlyConfig>;
213198

214-
deprecated query predicate problems(
215-
DataFlow::Node sinkNode, MissingHttpOnlyFlow::PathNode source, MissingHttpOnlyFlow::PathNode sink,
216-
string message1, DataFlow::Node sourceNode, string message2
217-
) {
218-
MissingHttpOnlyFlow::flowPath(source, sink) and
219-
sinkNode = sink.getNode() and
220-
message1 = "$@ doesn't have the HttpOnly flag set." and
221-
sourceNode = source.getNode() and
222-
message2 = "This sensitive cookie"
223-
}
199+
from MissingHttpOnlyFlow::PathNode source, MissingHttpOnlyFlow::PathNode sink
200+
where MissingHttpOnlyFlow::flowPath(source, sink)
201+
select sink, source, sink, "$@ doesn't have the HttpOnly flag set.", source, "This sensitive cookie"

0 commit comments

Comments
 (0)