mirror of
https://github.com/github/codeql.git
synced 2026-04-26 09:15:12 +02:00
V1
This commit is contained in:
@@ -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>
|
||||
127
java/ql/src/experimental/Security/CWE/CWE-347/Auth0NoVerifier.ql
Normal file
127
java/ql/src/experimental/Security/CWE/CWE-347/Auth0NoVerifier.ql
Normal 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"
|
||||
59
java/ql/src/experimental/Security/CWE/CWE-347/Example.java
Normal file
59
java/ql/src/experimental/Security/CWE/CWE-347/Example.java
Normal 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("");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user