forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSensitiveCookieNotHttpOnly.ql
More file actions
202 lines (176 loc) · 7.96 KB
/
SensitiveCookieNotHttpOnly.ql
File metadata and controls
202 lines (176 loc) · 7.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/**
* @name Sensitive cookies without the HttpOnly response header set
* @description Sensitive cookies without the 'HttpOnly' flag set leaves session cookies vulnerable to
* an XSS attack.
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id java/sensitive-cookie-not-httponly
* @tags security
* external/cwe/cwe-1004
*/
/*
* Sketch of the structure of this query: we track cookie names that appear to be sensitive
* (e.g. `session` or `token`) to a `ServletResponse.addHeader(...)` or `.addCookie(...)`
* method that does not set the `httpOnly` flag. Subsidiary configurations
* `MatchesHttpOnlyToRawHeaderConfig` and `SetHttpOnlyInCookieConfig` are used to establish
* when the `httpOnly` flag is likely to have been set, before configuration
* `MissingHttpOnlyConfig` establishes that a non-`httpOnly` cookie has a sensitive-seeming name.
*/
import java
import semmle.code.java.dataflow.FlowSteps
import semmle.code.java.frameworks.Servlets
import semmle.code.java.dataflow.TaintTracking
/** Gets a regular expression for matching common names of sensitive cookies. */
string getSensitiveCookieNameRegex() { result = "(?i).*(auth|session|token|key|credential).*" }
/** Gets a regular expression for matching CSRF cookies. */
string getCsrfCookieNameRegex() { result = "(?i).*(csrf).*" }
/**
* Holds if a string is concatenated with the name of a sensitive cookie. Excludes CSRF cookies since
* they are special cookies implementing the Synchronizer Token Pattern that can be used in JavaScript.
*/
predicate isSensitiveCookieNameExpr(Expr expr) {
exists(string s | s = expr.(CompileTimeConstantExpr).getStringValue() |
s.regexpMatch(getSensitiveCookieNameRegex()) and not s.regexpMatch(getCsrfCookieNameRegex())
)
or
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
}
/** A sensitive cookie name. */
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) }
}
/** A method call that sets a `Set-Cookie` header. */
class SetCookieRawHeaderMethodCall extends MethodCall {
SetCookieRawHeaderMethodCall() {
(
this.getMethod() instanceof ResponseAddHeaderMethod or
this.getMethod() instanceof ResponseSetHeaderMethod
) and
this.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "set-cookie"
}
}
/**
* A taint configuration tracking flow from the text `httponly` to argument 1 of
* `SetCookieRawHeaderMethodCall`.
*/
module MatchesHttpOnlyToRawHeaderConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr().(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
}
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(SetCookieRawHeaderMethodCall ma).getArgument(1)
}
}
module MatchesHttpOnlyToRawHeaderFlow = TaintTracking::Global<MatchesHttpOnlyToRawHeaderConfig>;
/** A class descended from `javax.servlet.http.Cookie`. */
class CookieClass extends RefType {
CookieClass() { this.getAnAncestor().hasQualifiedName("javax.servlet.http", "Cookie") }
}
/** Holds if `expr` is any boolean-typed expression other than literal `false`. */
// Inlined because this could be a very large result set if computed out of context
pragma[inline]
predicate mayBeBooleanTrue(Expr expr) {
expr.getType() instanceof BooleanType and
not expr.(CompileTimeConstantExpr).getBooleanValue() = false
}
/** Holds if the method call may set the `HttpOnly` flag. */
predicate setsCookieHttpOnly(MethodCall ma) {
ma.getMethod().getName() = "setHttpOnly" and
// any use of setHttpOnly(x) where x isn't false is probably safe
mayBeBooleanTrue(ma.getArgument(0))
}
/** Holds if `ma` removes a cookie. */
predicate removesCookie(MethodCall ma) {
ma.getMethod().getName() = "setMaxAge" and
ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
}
/**
* A taint configuration tracking flow of a method that sets the `HttpOnly` flag,
* or one that removes a cookie, to a `ServletResponse.addCookie` call.
*/
module SetHttpOnlyOrRemovesCookieToAddCookieConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr() =
any(MethodCall ma | setsCookieHttpOnly(ma) or removesCookie(ma)).getQualifier()
}
predicate isSink(DataFlow::Node sink) {
sink.asExpr() =
any(MethodCall ma | ma.getMethod() instanceof ResponseAddCookieMethod).getArgument(0)
}
}
module SetHttpOnlyOrRemovesCookieToAddCookieFlow =
TaintTracking::Global<SetHttpOnlyOrRemovesCookieToAddCookieConfig>;
/**
* A cookie that is added to an HTTP response and which doesn't have `httpOnly` set, used as a sink
* in `MissingHttpOnlyConfiguration`.
*/
class CookieResponseWithoutHttpOnlySink extends DataFlow::ExprNode {
CookieResponseWithoutHttpOnlySink() {
exists(MethodCall ma |
(
ma.getMethod() instanceof ResponseAddCookieMethod and
this.getExpr() = ma.getArgument(0) and
not SetHttpOnlyOrRemovesCookieToAddCookieFlow::flowTo(this)
or
ma instanceof SetCookieRawHeaderMethodCall and
this.getExpr() = ma.getArgument(1) and
not MatchesHttpOnlyToRawHeaderFlow::flowTo(this) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
)
)
}
}
/** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
predicate setsHttpOnlyInNewCookie(ClassInstanceExpr cie) {
cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
(
cie.getNumArgument() = 6 and
mayBeBooleanTrue(cie.getArgument(5)) // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 8 and
cie.getArgument(6).getType() instanceof BooleanType and
mayBeBooleanTrue(cie.getArgument(7)) // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 10 and
mayBeBooleanTrue(cie.getArgument(9)) // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
)
}
/**
* A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
* set to its HTTP response.
* Tracks string literals containing sensitive names (`SensitiveCookieNameExpr`), to an `addCookie` call (as a `Cookie` object)
* or an `addHeader` call (as a string) (`CookieResponseWithoutHttpOnlySink`).
* Passes through `Cookie` constructors and `toString` calls.
*/
module MissingHttpOnlyConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SensitiveCookieNameExpr }
predicate isSink(DataFlow::Node sink) { sink instanceof CookieResponseWithoutHttpOnlySink }
predicate isBarrier(DataFlow::Node node) {
// JAX-RS's `new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)` and similar
// Cookie constructors, but barriers to considering the flow of the sensitive name, as httponly flag is set.
setsHttpOnlyInNewCookie(node.asExpr())
}
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(
ConstructorCall cc // new Cookie(...)
|
cc.getConstructedType() instanceof CookieClass and
pred.asExpr() = cc.getAnArgument() and
succ.asExpr() = cc
)
or
exists(
MethodCall ma // cookie.toString()
|
ma.getMethod().getName() = "toString" and
ma.getQualifier().getType() instanceof CookieClass and
pred.asExpr() = ma.getQualifier() and
succ.asExpr() = ma
)
}
}
module MissingHttpOnlyFlow = TaintTracking::Global<MissingHttpOnlyConfig>;
import MissingHttpOnlyFlow::PathGraph
from MissingHttpOnlyFlow::PathNode source, MissingHttpOnlyFlow::PathNode sink
where MissingHttpOnlyFlow::flowPath(source, sink)
select sink, source, sink, "$@ doesn't have the HttpOnly flag set.", source, "This sensitive cookie"