This commit is contained in:
amammad
2023-08-29 22:19:37 +10:00
parent 7723dbc6d7
commit 664890ab33
3 changed files with 220 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
A JSON Web Token (JWT) is used for authenticating and managing users in an application.
</p>
<p>
Only Decoding JWTs without checking if they have a valid signature or not can lead to security vulnerabilities.
</p>
</overview>
<recommendation>
<p>
Don't use methods that only decode JWT, Instead use methods that verify the signature of JWT.
</p>
</recommendation>
<example>
<p>
The following code you can see an Example from a popular Library.
</p>
<sample src="Example.java" />
</example>
<references>
<li>
<a href="CVE-2021-37580">The incorrect use of JWT in ShenyuAdminBootstrap allows an attacker to bypass authentication.</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,127 @@
/**
* @name Missing JWT signature check
* @description Failing to check the Json Web Token (JWT) signature may allow an attacker to forge their own tokens.
* @kind path-problem
* @problem.severity error
* @security-severity 7.8
* @precision high
* @id java/missing-jwt-signature-check
* @tags security
* external/cwe/cwe-347
*/
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
module JwtAuth0 {
class PayloadType extends RefType {
PayloadType() { this.hasQualifiedName("com.auth0.jwt.interfaces", "Payload") }
}
class JWTType extends RefType {
JWTType() { this.hasQualifiedName("com.auth0.jwt", "JWT") }
}
class JWTVerifierType extends RefType {
JWTVerifierType() { this.hasQualifiedName("com.auth0.jwt", "JWTVerifier") }
}
class GetPayload extends MethodAccess {
GetPayload() {
this.getCallee().getDeclaringType() instanceof PayloadType and
this.getCallee().hasName(["getClaim", "getIssuedAt"])
}
}
class Decode extends MethodAccess {
Decode() {
this.getCallee().getDeclaringType() instanceof JWTType and
this.getCallee().hasName("decode")
}
}
class Verify extends MethodAccess {
Verify() {
this.getCallee().getDeclaringType() instanceof JWTVerifierType and
this.getCallee().hasName("verify")
}
}
}
module JwtDecodeConfig implements DataFlow::StateConfigSig {
class FlowState = DataFlow::FlowState;
predicate isSource(DataFlow::Node source, FlowState state) {
(
exists(Variable v |
source.asExpr() = v.getInitializer() and
v.getType().hasName("String")
)
or
source instanceof RemoteFlowSource
) and
not FlowToJwtVerify::flow(source, _) and
state = "Auth0" and
not state = "Auth0Verify"
}
predicate isSink(DataFlow::Node sink, FlowState state) {
sink.asExpr() = any(JwtAuth0::GetPayload a) and
state = "Auth0" and
not state = "Auth0Verify"
}
predicate isAdditionalFlowStep(
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
) {
// Decode Should be one of the middle nodes
exists(JwtAuth0::Decode a |
nodeFrom.asExpr() = a.getArgument(0) and
nodeTo.asExpr() = a and
stateTo = "Auth0" and
stateFrom = "Auth0"
)
or
exists(JwtAuth0::Verify a |
nodeFrom.asExpr() = a.getArgument(0) and
nodeTo.asExpr() = a and
stateTo = "Auth0Verify" and
stateFrom = "Auth0Verify"
)
or
exists(JwtAuth0::GetPayload a |
nodeFrom.asExpr() = a.getQualifier() and
nodeTo.asExpr() = a and
stateTo = "Auth0" and
stateFrom = "Auth0"
)
}
predicate isBarrier(DataFlow::Node sanitizer, FlowState state) { none() }
}
module FlowToJwtVerifyConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
// source instanceof DataFlow::Node
exists(Variable v |
source.asExpr() = v.getInitializer() and
v.getType().hasName("String")
)
}
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(JwtAuth0::Verify a).getArgument(0) }
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
}
module JwtDecode = TaintTracking::GlobalWithState<JwtDecodeConfig>;
module FlowToJwtVerify = TaintTracking::Global<FlowToJwtVerifyConfig>;
import JwtDecode::PathGraph
from JwtDecode::PathNode source, JwtDecode::PathNode sink
where JwtDecode::flowPath(source, sink)
select sink.getNode(), source, sink, "This parses a $@, but the signature is not verified.",
source.getNode(), "JWT"

View File

@@ -0,0 +1,59 @@
package com.example.JwtTest;
import java.io.*;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;
import javax.crypto.KeyGenerator;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
@WebServlet(name = "Jwt", value = "/Auth")
public class auth0 extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) {}
final String JWT_KEY = "KEY";
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// OK
String JwtToken1 = request.getParameter("JWT1");
decodeToken(JwtToken1);
try {
verifyToken(JwtToken1, getSecureRandomKey());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
// only decode, no verification
String JwtToken2 = request.getParameter("JWT2");
decodeToken(JwtToken2);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><body>heyyy</body></html>");
}
public static boolean verifyToken(final String token, final String key) {
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(key)).build();
verifier.verify(token);
return true;
} catch (JWTVerificationException e) {
System.out.printf("jwt decode fail, token: %s", e);
}
return false;
}
public static String decodeToken(final String token) {
DecodedJWT jwt = JWT.decode(token);
return Optional.of(jwt).map(item -> item.getClaim("userName").asString()).orElse("");
}
}