Skip to content

Commit 664890a

Browse files
committed
V1
1 parent 7723dbc commit 664890a

File tree

3 files changed

+220
-0
lines changed

3 files changed

+220
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
6+
</p>
7+
<p>
8+
Only Decoding JWTs without checking if they have a valid signature or not can lead to security vulnerabilities.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
Don't use methods that only decode JWT, Instead use methods that verify the signature of JWT.
16+
</p>
17+
18+
</recommendation>
19+
<example>
20+
21+
<p>
22+
The following code you can see an Example from a popular Library.
23+
</p>
24+
25+
<sample src="Example.java" />
26+
27+
</example>
28+
<references>
29+
<li>
30+
<a href="CVE-2021-37580">The incorrect use of JWT in ShenyuAdminBootstrap allows an attacker to bypass authentication.</a>
31+
</li>
32+
</references>
33+
34+
</qhelp>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* @name Missing JWT signature check
3+
* @description Failing to check the Json Web Token (JWT) signature may allow an attacker to forge their own tokens.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @security-severity 7.8
7+
* @precision high
8+
* @id java/missing-jwt-signature-check
9+
* @tags security
10+
* external/cwe/cwe-347
11+
*/
12+
13+
import java
14+
import semmle.code.java.dataflow.DataFlow
15+
import semmle.code.java.dataflow.FlowSources
16+
17+
module JwtAuth0 {
18+
class PayloadType extends RefType {
19+
PayloadType() { this.hasQualifiedName("com.auth0.jwt.interfaces", "Payload") }
20+
}
21+
22+
class JWTType extends RefType {
23+
JWTType() { this.hasQualifiedName("com.auth0.jwt", "JWT") }
24+
}
25+
26+
class JWTVerifierType extends RefType {
27+
JWTVerifierType() { this.hasQualifiedName("com.auth0.jwt", "JWTVerifier") }
28+
}
29+
30+
class GetPayload extends MethodAccess {
31+
GetPayload() {
32+
this.getCallee().getDeclaringType() instanceof PayloadType and
33+
this.getCallee().hasName(["getClaim", "getIssuedAt"])
34+
}
35+
}
36+
37+
class Decode extends MethodAccess {
38+
Decode() {
39+
this.getCallee().getDeclaringType() instanceof JWTType and
40+
this.getCallee().hasName("decode")
41+
}
42+
}
43+
44+
class Verify extends MethodAccess {
45+
Verify() {
46+
this.getCallee().getDeclaringType() instanceof JWTVerifierType and
47+
this.getCallee().hasName("verify")
48+
}
49+
}
50+
}
51+
52+
module JwtDecodeConfig implements DataFlow::StateConfigSig {
53+
class FlowState = DataFlow::FlowState;
54+
55+
predicate isSource(DataFlow::Node source, FlowState state) {
56+
(
57+
exists(Variable v |
58+
source.asExpr() = v.getInitializer() and
59+
v.getType().hasName("String")
60+
)
61+
or
62+
source instanceof RemoteFlowSource
63+
) and
64+
not FlowToJwtVerify::flow(source, _) and
65+
state = "Auth0" and
66+
not state = "Auth0Verify"
67+
}
68+
69+
predicate isSink(DataFlow::Node sink, FlowState state) {
70+
sink.asExpr() = any(JwtAuth0::GetPayload a) and
71+
state = "Auth0" and
72+
not state = "Auth0Verify"
73+
}
74+
75+
predicate isAdditionalFlowStep(
76+
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
77+
) {
78+
// Decode Should be one of the middle nodes
79+
exists(JwtAuth0::Decode a |
80+
nodeFrom.asExpr() = a.getArgument(0) and
81+
nodeTo.asExpr() = a and
82+
stateTo = "Auth0" and
83+
stateFrom = "Auth0"
84+
)
85+
or
86+
exists(JwtAuth0::Verify a |
87+
nodeFrom.asExpr() = a.getArgument(0) and
88+
nodeTo.asExpr() = a and
89+
stateTo = "Auth0Verify" and
90+
stateFrom = "Auth0Verify"
91+
)
92+
or
93+
exists(JwtAuth0::GetPayload a |
94+
nodeFrom.asExpr() = a.getQualifier() and
95+
nodeTo.asExpr() = a and
96+
stateTo = "Auth0" and
97+
stateFrom = "Auth0"
98+
)
99+
}
100+
101+
predicate isBarrier(DataFlow::Node sanitizer, FlowState state) { none() }
102+
}
103+
104+
module FlowToJwtVerifyConfig implements DataFlow::ConfigSig {
105+
predicate isSource(DataFlow::Node source) {
106+
// source instanceof DataFlow::Node
107+
exists(Variable v |
108+
source.asExpr() = v.getInitializer() and
109+
v.getType().hasName("String")
110+
)
111+
}
112+
113+
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(JwtAuth0::Verify a).getArgument(0) }
114+
115+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
116+
}
117+
118+
module JwtDecode = TaintTracking::GlobalWithState<JwtDecodeConfig>;
119+
120+
module FlowToJwtVerify = TaintTracking::Global<FlowToJwtVerifyConfig>;
121+
122+
import JwtDecode::PathGraph
123+
124+
from JwtDecode::PathNode source, JwtDecode::PathNode sink
125+
where JwtDecode::flowPath(source, sink)
126+
select sink.getNode(), source, sink, "This parses a $@, but the signature is not verified.",
127+
source.getNode(), "JWT"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.example.JwtTest;
2+
3+
import java.io.*;
4+
import java.security.NoSuchAlgorithmException;
5+
import java.util.Optional;
6+
import javax.crypto.KeyGenerator;
7+
import javax.servlet.http.*;
8+
import javax.servlet.annotation.*;
9+
import com.auth0.jwt.JWT;
10+
import com.auth0.jwt.JWTVerifier;
11+
import com.auth0.jwt.algorithms.Algorithm;
12+
import com.auth0.jwt.exceptions.JWTCreationException;
13+
import com.auth0.jwt.exceptions.JWTVerificationException;
14+
import com.auth0.jwt.interfaces.DecodedJWT;
15+
16+
@WebServlet(name = "Jwt", value = "/Auth")
17+
public class auth0 extends HttpServlet {
18+
19+
public void doPost(HttpServletRequest request, HttpServletResponse response) {}
20+
21+
final String JWT_KEY = "KEY";
22+
23+
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
24+
25+
// OK
26+
String JwtToken1 = request.getParameter("JWT1");
27+
decodeToken(JwtToken1);
28+
try {
29+
verifyToken(JwtToken1, getSecureRandomKey());
30+
} catch (NoSuchAlgorithmException e) {
31+
throw new RuntimeException(e);
32+
}
33+
34+
// only decode, no verification
35+
String JwtToken2 = request.getParameter("JWT2");
36+
decodeToken(JwtToken2);
37+
38+
39+
response.setContentType("text/html");
40+
PrintWriter out = response.getWriter();
41+
out.println("<html><body>heyyy</body></html>");
42+
}
43+
44+
public static boolean verifyToken(final String token, final String key) {
45+
try {
46+
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(key)).build();
47+
verifier.verify(token);
48+
return true;
49+
} catch (JWTVerificationException e) {
50+
System.out.printf("jwt decode fail, token: %s", e);
51+
}
52+
return false;
53+
}
54+
55+
public static String decodeToken(final String token) {
56+
DecodedJWT jwt = JWT.decode(token);
57+
return Optional.of(jwt).map(item -> item.getClaim("userName").asString()).orElse("");
58+
}
59+
}

0 commit comments

Comments
 (0)