Skip to content

Commit 22c9baa

Browse files
committed
Refactor JWT.qll
1 parent 430d9f1 commit 22c9baa

File tree

4 files changed

+103
-100
lines changed

4 files changed

+103
-100
lines changed

java/ql/src/Security/CWE/CWE-347/MissingJWTSignatureCheck.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*/
1111

1212
import java
13-
import semmle.code.java.security.JWT
13+
import semmle.code.java.security.JWTQuery
1414

1515
from JwtParserWithInsecureParseSink sink, JwtParserWithSigningKeyExpr parserExpr
1616
where sink.asExpr() = parserExpr

java/ql/src/semmle/code/java/security/JWT.qll

Lines changed: 44 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,71 @@
11
/** Provides classes for working with JWT libraries. */
22

33
import java
4-
import semmle.code.java.dataflow.DataFlow
5-
import semmle.code.java.dataflow.ExternalFlow
6-
7-
/**
8-
* An expr that is a (sub-type of) `JwtParser` for which a signing key has been set
9-
*/
10-
class JwtParserWithSigningKeyExpr extends Expr {
11-
MethodAccess signingMa;
12-
13-
JwtParserWithSigningKeyExpr() { isSigningKeySetter(this, signingMa) }
14-
15-
/** Gets the method access that sets the signing key for this parser. */
16-
MethodAccess getSigningMethodAccess() { result = signingMa }
4+
private import semmle.code.java.dataflow.ExternalFlow
5+
private import semmle.code.java.dataflow.DataFlow
6+
7+
/** A method access that assigns signing keys to a JWT parser. */
8+
class JwtParserWithInsecureParseSource extends DataFlow::Node {
9+
JwtParserWithInsecureParseSource() {
10+
exists(MethodAccess ma, Method m |
11+
m.getDeclaringType().getASupertype*() instanceof TypeJwtParser or
12+
m.getDeclaringType().getASupertype*() instanceof TypeJwtParserBuilder
13+
|
14+
this.asExpr() = ma and
15+
ma.getMethod() = m and
16+
m.hasName(["setSigningKey", "setSigningKeyResolver"])
17+
)
18+
}
1719
}
1820

1921
/**
2022
* The qualifier of an insecure parsing method.
21-
* That is, either the qualifier of a call to a `parse(token)`, `parseClaimsJwt(token)` or `parsePlaintextJwt(token)` method or
22-
* the qualifier of a call to a `parse(token, handler)` method where the `handler` is considered insecure.
23+
* That is, either the qualifier of a call to the `parse(token)`,
24+
* `parseClaimsJwt(token)` or `parsePlaintextJwt(token)` methods or
25+
* the qualifier of a call to a `parse(token, handler)` method
26+
* where the `handler` is considered insecure.
2327
*/
2428
class JwtParserWithInsecureParseSink extends DataFlow::Node {
2529
MethodAccess insecureParseMa;
2630

2731
JwtParserWithInsecureParseSink() {
2832
insecureParseMa.getQualifier() = this.asExpr() and
29-
(
30-
sinkNode(this, "jwt-insecure-parse")
31-
or
32-
sinkNode(this, "jwt-insecure-parse-handler") and
33-
isInsecureJwtHandler(insecureParseMa.getArgument(1))
33+
exists(Method m |
34+
insecureParseMa.getMethod() = m and
35+
m.getDeclaringType().getASupertype*() instanceof TypeJwtParser and
36+
m.hasName(["parse", "parseClaimsJwt", "parsePlaintextJwt"]) and
37+
(
38+
m.getNumberOfParameters() = 1
39+
or
40+
isInsecureJwtHandler(insecureParseMa.getArgument(1))
41+
)
3442
)
3543
}
3644

3745
/** Gets the method access that does the insecure parsing. */
3846
MethodAccess getParseMethodAccess() { result = insecureParseMa }
3947
}
4048

41-
/**
42-
* Holds if `signingMa` directly or indirectly sets a signing key for `expr`, which is a `JwtParser`.
43-
* The `setSigningKey` and `setSigningKeyResolver` methods set a signing key for a `JwtParser`.
44-
*
45-
* Directly means code like this (the signing key is set directly on a `JwtParser`):
46-
* ```java
47-
* Jwts.parser().setSigningKey(key).parse(token);
48-
* ```
49-
*
50-
* Indirectly means code like this (the signing key is set on a `JwtParserBuilder` indirectly setting the key of `JwtParser` that is created by the call to `build`):
51-
* ```java
52-
* Jwts.parserBuilder().setSigningKey(key).build().parse(token);
53-
* ```
54-
*/
55-
private predicate isSigningKeySetter(Expr expr, MethodAccess signingMa) {
56-
any(SigningToInsecureMethodAccessDataFlow s)
57-
.hasFlow(DataFlow::exprNode(signingMa), DataFlow::exprNode(expr))
49+
/** A set of additional taint steps to consider when taint tracking JWT related data flows. */
50+
class JwtParserWithInsecureParseAdditionalTaintStep extends Unit {
51+
predicate step(DataFlow::Node node1, DataFlow::Node node2) {
52+
jwtParserStep(node1.asExpr(), node2.asExpr())
53+
}
54+
}
55+
56+
/** Models the builder style of `JwtParser` and `JwtParserBuilder`. */
57+
private predicate jwtParserStep(Expr parser, MethodAccess ma) {
58+
(
59+
parser.getType().(RefType).getASourceSupertype*() instanceof TypeJwtParser or
60+
parser.getType().(RefType).getASourceSupertype*() instanceof TypeJwtParserBuilder
61+
) and
62+
ma.getQualifier() = parser
5863
}
5964

6065
/**
6166
* Holds if `parseHandlerExpr` is an insecure `JwtHandler`.
62-
* That is, it overrides a method from `JwtHandlerOnJwtMethod` and the override is not defined on `JwtHandlerAdapter`.
67+
* That is, it overrides a method from `JwtHandlerOnJwtMethod` and
68+
* the override is not defined on `JwtHandlerAdapter`.
6369
* `JwtHandlerAdapter`'s overrides are safe since they always throw an exception.
6470
*/
6571
private predicate isInsecureJwtHandler(Expr parseHandlerExpr) {
@@ -74,66 +80,6 @@ private predicate isInsecureJwtHandler(Expr parseHandlerExpr) {
7480
)
7581
}
7682

77-
/** CSV source models representing methods that assign signing keys to a JWT parser. */
78-
private class SigningKeySourceModel extends SourceModelCsv {
79-
override predicate row(string row) {
80-
row =
81-
[
82-
"io.jsonwebtoken;JwtParser;true;setSigningKey;;;ReturnValue;jwt-signing-key",
83-
"io.jsonwebtoken;JwtParser;true;setSigningKeyResolver;;;ReturnValue;jwt-signing-key",
84-
"io.jsonwebtoken;JwtParserBuilder;true;setSigningKey;;;ReturnValue;jwt-signing-key",
85-
"io.jsonwebtoken;JwtParserBuilder;true;setSigningKeyResolver;;;ReturnValue;jwt-signing-key"
86-
]
87-
}
88-
}
89-
90-
/** CSV sink models representing qualifiers of methods that parse a JWT insecurely. */
91-
private class InsecureJwtParseSinkModel extends SinkModelCsv {
92-
override predicate row(string row) {
93-
row =
94-
[
95-
"io.jsonwebtoken;JwtParser;true;parse;(String);;Argument[-1];jwt-insecure-parse",
96-
"io.jsonwebtoken;JwtParser;true;parseClaimsJwt;;;Argument[-1];jwt-insecure-parse",
97-
"io.jsonwebtoken;JwtParser;true;parsePlaintextJwt;;;Argument[-1];jwt-insecure-parse"
98-
]
99-
}
100-
}
101-
102-
/** CSV sink models representing qualifiers of methods that insecurely parse a JWT with a handler */
103-
private class InsecureJwtParseHandlerSinkModel extends SinkModelCsv {
104-
override predicate row(string row) {
105-
row =
106-
[
107-
"io.jsonwebtoken;JwtParser;true;parse;(String,JwtHandler);;Argument[-1];jwt-insecure-parse-handler"
108-
]
109-
}
110-
}
111-
112-
/** Models the builder style of `JwtParser` and `JwtParserBuilder`. */
113-
private predicate jwtParserStep(Expr parser, MethodAccess ma) {
114-
(
115-
parser.getType().(RefType).getASourceSupertype*() instanceof TypeJwtParser or
116-
parser.getType().(RefType).getASourceSupertype*() instanceof TypeJwtParserBuilder
117-
) and
118-
ma.getQualifier() = parser
119-
}
120-
121-
/**
122-
* Models flow from signing keys assignements to qualifiers of JWT insecure parsers.
123-
* This is used to determine whether a `JwtParser` has a signing key set.
124-
*/
125-
private class SigningToInsecureMethodAccessDataFlow extends DataFlow::Configuration {
126-
SigningToInsecureMethodAccessDataFlow() { this = "SigningToExprDataFlow" }
127-
128-
override predicate isSource(DataFlow::Node source) { sourceNode(source, "jwt-signing-key") }
129-
130-
override predicate isSink(DataFlow::Node sink) { sink instanceof JwtParserWithInsecureParseSink }
131-
132-
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
133-
jwtParserStep(node1.asExpr(), node2.asExpr())
134-
}
135-
}
136-
13783
/** The interface `io.jsonwebtoken.JwtParser`. */
13884
private class TypeJwtParser extends Interface {
13985
TypeJwtParser() { this.hasQualifiedName("io.jsonwebtoken", "JwtParser") }
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/** Provides classes to be used in queries related to JWT vulnerabilities. */
2+
3+
import java
4+
import semmle.code.java.dataflow.DataFlow
5+
import semmle.code.java.dataflow.ExternalFlow
6+
import semmle.code.java.security.JWT
7+
8+
/**
9+
* An expr that is a (sub-type of) `JwtParser` for which a signing key has been set.
10+
*/
11+
class JwtParserWithSigningKeyExpr extends Expr {
12+
MethodAccess signingMa;
13+
14+
JwtParserWithSigningKeyExpr() { isSigningKeySetter(this, signingMa) }
15+
16+
/** Gets the method access that sets the signing key for this parser. */
17+
MethodAccess getSigningMethodAccess() { result = signingMa }
18+
}
19+
20+
/**
21+
* Holds if `signingMa` directly or indirectly sets a signing key for `expr`, which is a `JwtParser`.
22+
* The `setSigningKey` and `setSigningKeyResolver` methods set a signing key for a `JwtParser`.
23+
*
24+
* Directly means code like this (the signing key is set directly on a `JwtParser`):
25+
* ```java
26+
* Jwts.parser().setSigningKey(key).parse(token);
27+
* ```
28+
*
29+
* Indirectly means code like this (the signing key is set on a `JwtParserBuilder` indirectly setting the key of `JwtParser` that is created by the call to `build`):
30+
* ```java
31+
* Jwts.parserBuilder().setSigningKey(key).build().parse(token);
32+
* ```
33+
*/
34+
private predicate isSigningKeySetter(Expr expr, MethodAccess signingMa) {
35+
any(SigningToInsecureMethodAccessDataFlow s)
36+
.hasFlow(DataFlow::exprNode(signingMa), DataFlow::exprNode(expr))
37+
}
38+
39+
/**
40+
* Models flow from signing keys assignements to qualifiers of JWT insecure parsers.
41+
* This is used to determine whether a `JwtParser` has a signing key set.
42+
*/
43+
private class SigningToInsecureMethodAccessDataFlow extends DataFlow::Configuration {
44+
SigningToInsecureMethodAccessDataFlow() { this = "SigningToExprDataFlow" }
45+
46+
override predicate isSource(DataFlow::Node source) {
47+
source instanceof JwtParserWithInsecureParseSource
48+
}
49+
50+
override predicate isSink(DataFlow::Node sink) { sink instanceof JwtParserWithInsecureParseSink }
51+
52+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
53+
any(JwtParserWithInsecureParseAdditionalTaintStep c).step(node1, node2)
54+
}
55+
}
56+
57+

java/ql/test/query-tests/security/CWE-347/MissingJWTSignatureCheckTest.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import java
2-
import semmle.code.java.security.JWT
2+
import semmle.code.java.security.JWTQuery
33
import TestUtilities.InlineExpectationsTest
44

55
class HasMissingJwtSignatureCheckTest extends InlineExpectationsTest {

0 commit comments

Comments
 (0)