-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathMissingJWTSignatureCheck.ql
More file actions
200 lines (177 loc) · 7.43 KB
/
MissingJWTSignatureCheck.ql
File metadata and controls
200 lines (177 loc) · 7.43 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
/**
* @name Missing JWT signature check
* @description Not checking the JWT signature allows an attacker to forge their own tokens.
* @kind problem
* @problem.severity error
* @precision high
* @id java/missing-jwt-signature-check
* @tags security
* external/cwe/cwe-347
*/
import java
import semmle.code.java.dataflow.DataFlow
/** The interface `io.jsonwebtoken.JwtParser`. */
class TypeJwtParser extends Interface {
TypeJwtParser() { this.hasQualifiedName("io.jsonwebtoken", "JwtParser") }
}
/** The interface `io.jsonwebtoken.JwtParser` or a type derived from it. */
class TypeDerivedJwtParser extends RefType {
TypeDerivedJwtParser() { this.getASourceSupertype*() instanceof TypeJwtParser }
}
/** The interface `io.jsonwebtoken.JwtParserBuilder`. */
class TypeJwtParserBuilder extends Interface {
TypeJwtParserBuilder() { this.hasQualifiedName("io.jsonwebtoken", "JwtParserBuilder") }
}
/** The interface `io.jsonwebtoken.JwtHandler`. */
class TypeJwtHandler extends Interface {
TypeJwtHandler() { this.hasQualifiedName("io.jsonwebtoken", "JwtHandler") }
}
/** The class `io.jsonwebtoken.JwtHandlerAdapter`. */
class TypeJwtHandlerAdapter extends Class {
TypeJwtHandlerAdapter() { this.hasQualifiedName("io.jsonwebtoken", "JwtHandlerAdapter") }
}
/** The `parse(token, handler)` method defined in `JwtParser`. */
private class JwtParserParseHandlerMethod extends Method {
JwtParserParseHandlerMethod() {
this.hasName("parse") and
this.getDeclaringType() instanceof TypeJwtParser and
this.getNumberOfParameters() = 2
}
}
/** The `parse(token)`, `parseClaimsJwt(token)` and `parsePlaintextJwt(token)` methods defined in `JwtParser`. */
private class JwtParserInsecureParseMethod extends Method {
JwtParserInsecureParseMethod() {
this.hasName(["parse", "parseClaimsJwt", "parsePlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtParser
}
}
/** The `on(Claims|Plaintext)Jwt` methods defined in `JwtHandler`. */
private class JwtHandlerOnJwtMethod extends Method {
JwtHandlerOnJwtMethod() {
this.hasName(["onClaimsJwt", "onPlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtHandler
}
}
/** The `on(Claims|Plaintext)Jwt` methods defined in `JwtHandlerAdapter`. */
private class JwtHandlerAdapterOnJwtMethod extends Method {
JwtHandlerAdapterOnJwtMethod() {
this.hasName(["onClaimsJwt", "onPlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtHandlerAdapter
}
}
/**
* Holds if `parseHandlerExpr` is an insecure `JwtHandler`.
* That is, it overrides a method from `JwtHandlerOnJwtMethod` and the override is not defined on `JwtHandlerAdapter`.
* `JwtHandlerAdapter`'s overrides are safe since they always throw an exception.
*/
private predicate isInsecureJwtHandler(Expr parseHandlerExpr) {
exists(RefType t |
parseHandlerExpr.getType() = t and
t.getASourceSupertype*() instanceof TypeJwtHandler and
exists(Method m |
m = t.getAMethod() and
m.getASourceOverriddenMethod+() instanceof JwtHandlerOnJwtMethod and
not m.getSourceDeclaration() instanceof JwtHandlerAdapterOnJwtMethod
)
)
}
/**
* An access to an insecure parsing method.
* That is, either a call to a `parse(token)`, `parseClaimsJwt(token)` or `parsePlaintextJwt(token)` method or
* a call to a `parse(token, handler)` method where the `handler` is considered insecure.
*/
private class JwtParserInsecureParseMethodAccess extends MethodAccess {
JwtParserInsecureParseMethodAccess() {
this.getMethod().getASourceOverriddenMethod*() instanceof JwtParserInsecureParseMethod
or
this.getMethod().getASourceOverriddenMethod*() instanceof JwtParserParseHandlerMethod and
isInsecureJwtHandler(this.getArgument(1))
}
}
/**
* Holds if `signingMa` directly or indirectly sets a signing key for `expr`, which is a `JwtParser`.
* The `setSigningKey` and `setSigningKeyResolver` methods set a signing key for a `JwtParser`.
* Directly means code like this:
* ```java
* Jwts.parser().setSigningKey(key).parse(token);
* ```
* Here the signing key is set directly on a `JwtParser`.
* Indirectly means code like this:
* ```java
* Jwts.parserBuilder().setSigningKey(key).build().parse(token);
* ```
* In this case, the signing key is set on a `JwtParserBuilder` indirectly setting the key of `JwtParser` that is created by the call to `build`.
*/
private predicate isSigningKeySetter(Expr expr, MethodAccess signingMa) {
any(SigningToInsecureMethodAccessDataFlow s)
.hasFlow(DataFlow::exprNode(signingMa), DataFlow::exprNode(expr))
}
/**
* An expr that is a (sub-type of) `JwtParser` for which a signing key has been set and which is used as
* the qualifier to a `JwtParserInsecureParseMethodAccess`.
*/
private class JwtParserWithSigningKeyExpr extends Expr {
MethodAccess signingMa;
JwtParserWithSigningKeyExpr() {
this.getType() instanceof TypeDerivedJwtParser and
isSigningKeySetter(this, signingMa)
}
/** Gets the method access that sets the signing key for this parser. */
MethodAccess getSigningMethodAccess() { result = signingMa }
}
/**
* Models flow from `SigningKeyMethodAccess`es to qualifiers of `JwtParserInsecureParseMethodAccess`es.
* This is used to determine whether a `JwtParser` has a signing key set.
*/
private class SigningToInsecureMethodAccessDataFlow extends DataFlow::Configuration {
SigningToInsecureMethodAccessDataFlow() { this = "SigningToExprDataFlow" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof SigningKeyMethodAccess
}
override predicate isSink(DataFlow::Node sink) {
any(JwtParserInsecureParseMethodAccess ma).getQualifier() = sink.asExpr()
}
/** Models the builder style of `JwtParser` and `JwtParserBuilder`. */
override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
(
pred.asExpr().getType() instanceof TypeDerivedJwtParser or
pred.asExpr().getType().(RefType).getASourceSupertype*() instanceof TypeJwtParserBuilder
) and
succ.asExpr().(MethodAccess).getQualifier() = pred.asExpr()
}
}
/** An access to the `setSigningKey` or `setSigningKeyResolver` method (or an overridden method) defined in `JwtParser` and `JwtParserBuilder`. */
private class SigningKeyMethodAccess extends MethodAccess {
SigningKeyMethodAccess() {
exists(Method m |
m.hasName(["setSigningKey", "setSigningKeyResolver"]) and
m.getNumberOfParameters() = 1 and
(
m.getDeclaringType() instanceof TypeJwtParser or
m.getDeclaringType() instanceof TypeJwtParserBuilder
)
|
m = this.getMethod().getASourceOverriddenMethod*()
)
}
}
/**
* Holds if the `MethodAccess` `ma` occurs in a test file. A test file is any file that
* is a direct or indirect child of a directory named `test`, ignoring case.
*/
private predicate isInTestFile(MethodAccess ma) {
exists(string lowerCasedAbsolutePath |
lowerCasedAbsolutePath = ma.getLocation().getFile().getAbsolutePath().toLowerCase()
|
lowerCasedAbsolutePath.matches("%/test/%") and
not lowerCasedAbsolutePath
.matches("%/ql/test/experimental/query-tests/security/CWE-347/%".toLowerCase())
)
}
from JwtParserInsecureParseMethodAccess ma, JwtParserWithSigningKeyExpr parserExpr
where ma.getQualifier() = parserExpr and not isInTestFile(ma)
select ma, "A signing key is set $@, but the signature is not verified.",
parserExpr.getSigningMethodAccess(), "here"