Skip to content

Commit d1462ed

Browse files
committed
[Java] Add "missing jwt signature check" query.
1 parent 20416ae commit d1462ed

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/**
2+
* @name Missing JWT signature check
3+
* @description Not checking the JWT signature allows an attacker to forge their own tokens.
4+
* @kind problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id java/missing-jwt-signature-check
8+
* @tags security
9+
* external/cwe/cwe-347
10+
*/
11+
12+
import java
13+
import semmle.code.java.dataflow.DataFlow
14+
15+
/** The interface `io.jsonwebtoken.JwtParser`. */
16+
class TypeJwtParser extends Interface {
17+
TypeJwtParser() { hasQualifiedName("io.jsonwebtoken", "JwtParser") }
18+
}
19+
20+
/** The interface `io.jsonwebtoken.JwtParserBuilder`. */
21+
class TypeJwtParserBuilder extends Interface {
22+
TypeJwtParserBuilder() { hasQualifiedName("io.jsonwebtoken", "JwtParserBuilder") }
23+
}
24+
25+
/** The interface `io.jsonwebtoken.JwtHandler`. */
26+
class TypeJwtHandler extends Interface {
27+
TypeJwtHandler() { hasQualifiedName("io.jsonwebtoken", "JwtHandler") }
28+
}
29+
30+
/** The class `io.jsonwebtoken.JwtHandlerAdapter`. */
31+
class TypeJwtHandlerAdapter extends Class {
32+
TypeJwtHandlerAdapter() { hasQualifiedName("io.jsonwebtoken", "JwtHandlerAdapter") }
33+
}
34+
35+
/** The `parse(token, handler)` method defined in `TypeJwtParser`. */
36+
private class JwtParserParseHandlerMethod extends Method {
37+
JwtParserParseHandlerMethod() {
38+
hasName("parse") and
39+
getDeclaringType() instanceof TypeJwtParser and
40+
getNumberOfParameters() = 2
41+
}
42+
}
43+
44+
/** The `parse(token)`, `parseClaimsJwt(token)` and `parsePlaintextJwt(token)` methods defined in `TypeJwtParser`. */
45+
private class JwtParserInsecureParseMethods extends Method {
46+
JwtParserInsecureParseMethods() {
47+
hasName(["parse", "parseClaimsJwt", "parsePlaintextJwt"]) and
48+
getNumberOfParameters() = 1 and
49+
getDeclaringType() instanceof TypeJwtParser
50+
}
51+
}
52+
53+
/** The `onClaimsJwt(jwt)` and `onPlaintextJwt(jwt)` methods defined in `TypeJwtHandler`. */
54+
private class JwtHandlerOnJwtMethods extends Method {
55+
JwtHandlerOnJwtMethods() {
56+
hasName(["onClaimsJwt", "onPlaintextJwt"]) and
57+
getNumberOfParameters() = 1 and
58+
getDeclaringType() instanceof TypeJwtHandler
59+
}
60+
}
61+
62+
/** The `onClaimsJwt(jwt)` and `onPlaintextJwt(jwt)` methods defined in `TypeJwtHandlerAdapter`. */
63+
private class JwtHandlerAdapterOnJwtMethods extends Method {
64+
JwtHandlerAdapterOnJwtMethods() {
65+
hasName(["onClaimsJwt", "onPlaintextJwt"]) and
66+
getNumberOfParameters() = 1 and
67+
getDeclaringType() instanceof TypeJwtHandlerAdapter
68+
}
69+
}
70+
71+
/**
72+
* Holds if `parseHandlerExpr` is an insecure `JwtHandler`.
73+
* That is, it overrides a method from `JwtHandlerOnJwtMethods` and the overriden method is not a method from `JwtHandlerAdapterOnJwtMethods`.
74+
* A overriden method which is a method from `JwtHandlerAdapterOnJwtMethods` is safe, because these always throw an exception.
75+
*/
76+
private predicate isInsecureParseHandler(Expr parseHandlerExpr) {
77+
exists(RefType t |
78+
parseHandlerExpr.getType() = t and
79+
t.getASourceSupertype*() instanceof TypeJwtHandler and
80+
exists(Method m |
81+
m = t.getAMethod() and
82+
m.getASourceOverriddenMethod+() instanceof JwtHandlerOnJwtMethods and
83+
not m.getSourceDeclaration() instanceof JwtHandlerAdapterOnJwtMethods
84+
)
85+
)
86+
}
87+
88+
/**
89+
* An access to an insecure parsing method.
90+
* That is, either a call to a `parse(token)`, `parseClaimsJwt(token)` or `parsePlaintextJwt(token)` method or
91+
* a call to a `parse(token, handler)` method where the `handler` is considered insecure.
92+
*/
93+
private class JwtParserInsecureParseMethodAccess extends MethodAccess {
94+
JwtParserInsecureParseMethodAccess() {
95+
getMethod().getASourceOverriddenMethod*() instanceof JwtParserInsecureParseMethods
96+
or
97+
getMethod().getASourceOverriddenMethod*() instanceof JwtParserParseHandlerMethod and
98+
isInsecureParseHandler(this.getArgument(1))
99+
}
100+
}
101+
102+
/**
103+
* Holds if `signingMa` directly or indirectly sets a signing key for `expr`, which is a `TypeJwtParser`.
104+
* The `setSigningKey` and `setSigningKeyResolver` methods set a signing key for a `TypeJwtParser`.
105+
* Directly means code like this:
106+
* ```java
107+
* Jwts.parser().setSigningKey(key).parse(token);
108+
* ```
109+
* Here the signing key is set directly on a `TypeJwtParser`.
110+
* Indirectly means code like this:
111+
* ```java
112+
* Jwts.parserBuilder().setSigningKey(key).build().parse(token);
113+
* ```
114+
* In this case, the signing key is set on a `TypeJwtParserBuilder` indirectly setting the key of `TypeJwtParser` that is created by the call to `build`.
115+
*/
116+
private predicate isSigningKeySet(Expr expr, MethodAccess signingMa) {
117+
any(SigningToExprDataFlow s).hasFlow(DataFlow::exprNode(signingMa), DataFlow::exprNode(expr))
118+
}
119+
120+
/** An expr that is a `TypeJwtParser` for which a signing key has been set. */
121+
private class JwtParserWithSigningKeyExpr extends Expr {
122+
MethodAccess signingMa;
123+
124+
JwtParserWithSigningKeyExpr() {
125+
this.getType().(RefType).getASourceSupertype*() instanceof TypeJwtParser and
126+
isSigningKeySet(this, signingMa)
127+
}
128+
129+
/** Gets the method access that sets the signing key for this parser. */
130+
MethodAccess getSigningMethodAccess() { result = signingMa }
131+
}
132+
133+
/**
134+
* Models flow from `SigningKeyMethodAccess`es to expressions that are a (sub-type of) `TypeJwtParser`.
135+
* This is used to determine whether a `TypeJwtParser` has a signing key set.
136+
*/
137+
private class SigningToExprDataFlow extends DataFlow::Configuration {
138+
SigningToExprDataFlow() { this = "SigningToExprDataFlow" }
139+
140+
override predicate isSource(DataFlow::Node source) {
141+
source.asExpr() instanceof SigningKeyMethodAccess
142+
}
143+
144+
override predicate isSink(DataFlow::Node sink) {
145+
sink.asExpr().getType().(RefType).getASourceSupertype*() instanceof TypeJwtParser
146+
}
147+
148+
/** Models the builder style of `TypeJwtParser` and `TypeJwtParserBuilder`. */
149+
override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
150+
(
151+
pred.asExpr().getType().(RefType).getASourceSupertype*() instanceof TypeJwtParser or
152+
pred.asExpr().getType().(RefType).getASourceSupertype*() instanceof TypeJwtParserBuilder
153+
) and
154+
succ.asExpr().(MethodAccess).getQualifier() = pred.asExpr()
155+
}
156+
}
157+
158+
/** An access to the `setSigningKey` or `setSigningKeyResolver` method (or an overriden method) defined in `TypeJwtParser` and `TypeJwtParserBuilder`. */
159+
private class SigningKeyMethodAccess extends MethodAccess {
160+
SigningKeyMethodAccess() {
161+
exists(Method m |
162+
m.hasName(["setSigningKey", "setSigningKeyResolver"]) and
163+
m.getNumberOfParameters() = 1 and
164+
(
165+
m.getDeclaringType() instanceof TypeJwtParser or
166+
m.getDeclaringType() instanceof TypeJwtParserBuilder
167+
)
168+
|
169+
m = this.getMethod().getASourceOverriddenMethod*()
170+
)
171+
}
172+
}
173+
174+
from JwtParserInsecureParseMethodAccess ma, JwtParserWithSigningKeyExpr parserExpr
175+
where ma.getQualifier() = parserExpr
176+
select ma, "A signing key is set $@, but the signature is not verified.",
177+
parserExpr.getSigningMethodAccess(), "here"

0 commit comments

Comments
 (0)