mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #12032 from egregius313/egregius313/promote-hardcoded-jwt-credential
Java: Promote Hardcoded JWT credential query
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added new sinks for `java/hardcoded-credential-api-call` to identify the use of hardcoded secrets in the creation and verification of JWT tokens using `com.auth0.jwt`. These sinks are from [an experimental query submitted by @luchua](https://github.com/github/codeql/pull/9036).
|
||||
@@ -490,5 +490,11 @@ private predicate otherApiCallableCredentialParam(string s) {
|
||||
"com.microsoft.sqlserver.jdbc.SQLServerDataSource;setPassword(String);0",
|
||||
"com.microsoft.sqlserver.jdbc.SQLServerDataSource;getConnection(String, String);0",
|
||||
"com.microsoft.sqlserver.jdbc.SQLServerDataSource;getConnection(String, String);1",
|
||||
"com.auth0.jwt.algorithms.Algorithm;HMAC256(String);0",
|
||||
"com.auth0.jwt.algorithms.Algorithm;HMAC256(byte[]);0",
|
||||
"com.auth0.jwt.algorithms.Algorithm;HMAC384(String);0",
|
||||
"com.auth0.jwt.algorithms.Algorithm;HMAC384(byte[]);0",
|
||||
"com.auth0.jwt.algorithms.Algorithm;HMAC512(String);0",
|
||||
"com.auth0.jwt.algorithms.Algorithm;HMAC512(byte[]);0"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// BAD: Get secret from hardcoded string then sign a JWT token
|
||||
Algorithm algorithm = Algorithm.HMAC256("hardcoded_secret");
|
||||
JWT.create()
|
||||
.withClaim("username", username)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
// BAD: Get secret from hardcoded string then verify a JWT token
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("hardcoded_secret"))
|
||||
.withIssuer(ISSUER)
|
||||
.build();
|
||||
verifier.verify(token);
|
||||
|
||||
// GOOD: Get secret from system configuration then sign a token
|
||||
String tokenSecret = System.getenv("SECRET_KEY");
|
||||
Algorithm algorithm = Algorithm.HMAC256(tokenSecret);
|
||||
JWT.create()
|
||||
.withClaim("username", username)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
// GOOD: Get secret from environment variable then verify a JWT token
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(System.getenv("SECRET_KEY")))
|
||||
.withIssuer(ISSUER)
|
||||
.build();
|
||||
verifier.verify(token);
|
||||
@@ -1,46 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
JWT (JSON Web Token) is an open standard (RFC 7519) that defines a way to provide information
|
||||
within a JSON object between two parties. JWT is widely used for sharing security information
|
||||
between two parties in web applications. Each JWT contains encoded JSON objects, including a
|
||||
set of claims. JWTs are signed using a cryptographic algorithm to ensure that the claims cannot
|
||||
be altered after the token is issued.
|
||||
</p>
|
||||
<p>
|
||||
The most basic mistake is using hardcoded secrets for JWT generation/verification. This allows
|
||||
an attacker to forge the token if the source code (and JWT secret in it) is publicly exposed or
|
||||
leaked, which leads to authentication bypass or privilege escalation.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Generating a cryptographically secure secret key during application initialization and using this
|
||||
generated key for JWT signing/verification requests can prevent this vulnerability. Or safely store
|
||||
the secret key in a key vault that cannot be leaked in source code.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following examples show the bad case and the good case respectively. The <code>bad</code>
|
||||
methods show a hardcoded secret key is used to sign and verify JWT tokens. In the <code>good</code>
|
||||
method, the secret key is loaded from a system environment during application initialization.
|
||||
</p>
|
||||
<sample src="HardcodedJwtKey.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Semgrep Blog:
|
||||
<a href="https://r2c.dev/blog/2020/hardcoded-secrets-unverified-tokens-and-other-common-jwt-mistakes/">Hardcoded secrets, unverified tokens, and other common JWT mistakes</a>
|
||||
</li>
|
||||
<li>
|
||||
CVE-2022-24860:
|
||||
<a href="https://nvd.nist.gov/vuln/detail/CVE-2022-24860">Databasir 1.01 has Use of Hard-coded Cryptographic Key vulnerability.</a>
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @name Use of a hardcoded key for signing JWT
|
||||
* @description Using a hardcoded key for signing JWT can allow an attacker to compromise security.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @id java/hardcoded-jwt-key
|
||||
* @tags security
|
||||
* experimental
|
||||
* external/cwe/cwe-321
|
||||
*/
|
||||
|
||||
import java
|
||||
import HardcodedJwtKey
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, HardcodedJwtKeyConfiguration cfg
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ is used to sign a JWT token.", source.getNode(),
|
||||
"Hardcoded String"
|
||||
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* Provides sources and sinks for detecting JWT token signing vulnerabilities.
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
private class ActivateModels extends ActiveExperimentalModels {
|
||||
ActivateModels() { this = "hardcoded-jwt-key" }
|
||||
}
|
||||
|
||||
/** The class `com.auth0.jwt.JWT`. */
|
||||
class Jwt extends RefType {
|
||||
Jwt() { this.hasQualifiedName("com.auth0.jwt", "JWT") }
|
||||
}
|
||||
|
||||
/** The class `com.auth0.jwt.JWTCreator.Builder`. */
|
||||
class JwtBuilder extends RefType {
|
||||
JwtBuilder() { this.hasQualifiedName("com.auth0.jwt", "JWTCreator$Builder") }
|
||||
}
|
||||
|
||||
/** The class `com.auth0.jwt.algorithms.Algorithm`. */
|
||||
class JwtAlgorithm extends RefType {
|
||||
JwtAlgorithm() { this.hasQualifiedName("com.auth0.jwt.algorithms", "Algorithm") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface `com.auth0.jwt.interfaces.JWTVerifier` or its implementation
|
||||
* `com.auth0.jwt.JWTVerifier`.
|
||||
*/
|
||||
class JwtVerifier extends RefType {
|
||||
JwtVerifier() {
|
||||
this.hasQualifiedName(["com.auth0.jwt", "com.auth0.jwt.interfaces"], "JWTVerifier")
|
||||
}
|
||||
}
|
||||
|
||||
/** A method that creates an instance of `com.auth0.jwt.algorithms.Algorithm`. */
|
||||
class GetAlgorithmMethod extends Method {
|
||||
GetAlgorithmMethod() {
|
||||
this.getDeclaringType() instanceof JwtAlgorithm and
|
||||
this.getName().matches(["HMAC%", "ECDSA%", "RSA%"])
|
||||
}
|
||||
}
|
||||
|
||||
/** The `require` method of `com.auth0.jwt.JWT`. */
|
||||
class RequireMethod extends Method {
|
||||
RequireMethod() {
|
||||
this.getDeclaringType() instanceof Jwt and
|
||||
this.hasName("require")
|
||||
}
|
||||
}
|
||||
|
||||
/** The `sign` method of `com.auth0.jwt.JWTCreator.Builder`. */
|
||||
class SignTokenMethod extends Method {
|
||||
SignTokenMethod() {
|
||||
this.getDeclaringType() instanceof JwtBuilder and
|
||||
this.hasName("sign")
|
||||
}
|
||||
}
|
||||
|
||||
/** The `verify` method of `com.auth0.jwt.interfaces.JWTVerifier`. */
|
||||
class VerifyTokenMethod extends Method {
|
||||
VerifyTokenMethod() {
|
||||
this.getDeclaringType() instanceof JwtVerifier and
|
||||
this.hasName("verify")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow source for JWT token signing vulnerabilities.
|
||||
*/
|
||||
abstract class JwtKeySource extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for JWT token signing vulnerabilities.
|
||||
*/
|
||||
abstract class JwtTokenSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A hardcoded string literal as a source for JWT token signing vulnerabilities.
|
||||
*/
|
||||
class HardcodedKeyStringSource extends JwtKeySource {
|
||||
HardcodedKeyStringSource() { exists(this.asExpr().(CompileTimeConstantExpr).getStringValue()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression used to sign JWT tokens as a sink of JWT token signing vulnerabilities.
|
||||
*/
|
||||
private class SignTokenSink extends JwtTokenSink {
|
||||
SignTokenSink() {
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod() instanceof SignTokenMethod and
|
||||
this.asExpr() = ma.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression used to verify JWT tokens as a sink of JWT token signing vulnerabilities.
|
||||
*/
|
||||
private class VerifyTokenSink extends JwtTokenSink {
|
||||
VerifyTokenSink() {
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod() instanceof VerifyTokenMethod and
|
||||
this.asExpr() = ma.getQualifier()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration depicting taint flow for checking JWT token signing vulnerabilities.
|
||||
*/
|
||||
class HardcodedJwtKeyConfiguration extends TaintTracking::Configuration {
|
||||
HardcodedJwtKeyConfiguration() { this = "Hard-coded JWT Signing Key" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof JwtKeySource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof JwtTokenSink }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) {
|
||||
exists(MethodAccess ma |
|
||||
(
|
||||
ma.getMethod() instanceof GetAlgorithmMethod or
|
||||
ma.getMethod() instanceof RequireMethod
|
||||
) and
|
||||
prev.asExpr() = ma.getArgument(0) and
|
||||
succ.asExpr() = ma
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
edges
|
||||
| HardcodedJwtKey.java:15:33:15:38 | SECRET : String | HardcodedJwtKey.java:19:49:19:54 | SECRET : String |
|
||||
| HardcodedJwtKey.java:15:33:15:38 | SECRET : String | HardcodedJwtKey.java:42:62:42:67 | SECRET : String |
|
||||
| HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" : String | HardcodedJwtKey.java:15:33:15:38 | SECRET : String |
|
||||
| HardcodedJwtKey.java:19:49:19:54 | SECRET : String | HardcodedJwtKey.java:25:23:25:31 | algorithm |
|
||||
| HardcodedJwtKey.java:42:32:42:69 | require(...) : Verification | HardcodedJwtKey.java:42:32:43:35 | withIssuer(...) : Verification |
|
||||
| HardcodedJwtKey.java:42:32:43:35 | withIssuer(...) : Verification | HardcodedJwtKey.java:42:32:44:24 | build(...) : JWTVerifier |
|
||||
| HardcodedJwtKey.java:42:32:44:24 | build(...) : JWTVerifier | HardcodedJwtKey.java:46:13:46:20 | verifier |
|
||||
| HardcodedJwtKey.java:42:62:42:67 | SECRET : String | HardcodedJwtKey.java:42:32:42:69 | require(...) : Verification |
|
||||
nodes
|
||||
| HardcodedJwtKey.java:15:33:15:38 | SECRET : String | semmle.label | SECRET : String |
|
||||
| HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" : String | semmle.label | "hardcoded_secret" : String |
|
||||
| HardcodedJwtKey.java:19:49:19:54 | SECRET : String | semmle.label | SECRET : String |
|
||||
| HardcodedJwtKey.java:25:23:25:31 | algorithm | semmle.label | algorithm |
|
||||
| HardcodedJwtKey.java:42:32:42:69 | require(...) : Verification | semmle.label | require(...) : Verification |
|
||||
| HardcodedJwtKey.java:42:32:43:35 | withIssuer(...) : Verification | semmle.label | withIssuer(...) : Verification |
|
||||
| HardcodedJwtKey.java:42:32:44:24 | build(...) : JWTVerifier | semmle.label | build(...) : JWTVerifier |
|
||||
| HardcodedJwtKey.java:42:62:42:67 | SECRET : String | semmle.label | SECRET : String |
|
||||
| HardcodedJwtKey.java:46:13:46:20 | verifier | semmle.label | verifier |
|
||||
subpaths
|
||||
#select
|
||||
| HardcodedJwtKey.java:25:23:25:31 | algorithm | HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" : String | HardcodedJwtKey.java:25:23:25:31 | algorithm | $@ is used to sign a JWT token. | HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" | Hardcoded String |
|
||||
| HardcodedJwtKey.java:25:23:25:31 | algorithm | HardcodedJwtKey.java:19:49:19:54 | SECRET : String | HardcodedJwtKey.java:25:23:25:31 | algorithm | $@ is used to sign a JWT token. | HardcodedJwtKey.java:19:49:19:54 | SECRET | Hardcoded String |
|
||||
| HardcodedJwtKey.java:46:13:46:20 | verifier | HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" : String | HardcodedJwtKey.java:46:13:46:20 | verifier | $@ is used to sign a JWT token. | HardcodedJwtKey.java:15:42:15:59 | "hardcoded_secret" | Hardcoded String |
|
||||
| HardcodedJwtKey.java:46:13:46:20 | verifier | HardcodedJwtKey.java:42:62:42:67 | SECRET : String | HardcodedJwtKey.java:46:13:46:20 | verifier | $@ is used to sign a JWT token. | HardcodedJwtKey.java:42:62:42:67 | SECRET | Hardcoded String |
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE/CWE-321/HardcodedJwtKey.ql
|
||||
@@ -1 +0,0 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/auth0-jwt-2.3
|
||||
@@ -16,7 +16,7 @@ public class HardcodedJwtKey {
|
||||
|
||||
// BAD: Get secret from hardcoded string then sign a JWT token
|
||||
public String accessTokenBad(String username) {
|
||||
Algorithm algorithm = Algorithm.HMAC256(SECRET);
|
||||
Algorithm algorithm = Algorithm.HMAC256(SECRET); // $ HardcodedCredentialsApiCall
|
||||
|
||||
return JWT.create()
|
||||
.withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME))
|
||||
@@ -39,7 +39,7 @@ public class HardcodedJwtKey {
|
||||
|
||||
// BAD: Get secret from hardcoded string then verify a JWT token
|
||||
public boolean verifyTokenBad(String token) {
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
|
||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)) // $ HardcodedCredentialsApiCall
|
||||
.withIssuer(ISSUER)
|
||||
.build();
|
||||
try {
|
||||
@@ -62,4 +62,49 @@ public class HardcodedJwtKey {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String accessTokenBad384(String username) {
|
||||
Algorithm algorithm = Algorithm.HMAC384(SECRET); // $ HardcodedCredentialsApiCall
|
||||
|
||||
return JWT.create()
|
||||
.withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME))
|
||||
.withIssuer(ISSUER)
|
||||
.withClaim("username", username)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
// GOOD: Get secret from system configuration then sign a token
|
||||
public String accessTokenGood384(String username) {
|
||||
String tokenSecret = System.getenv("SECRET_KEY");
|
||||
Algorithm algorithm = Algorithm.HMAC384(tokenSecret);
|
||||
|
||||
return JWT.create()
|
||||
.withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME))
|
||||
.withIssuer(ISSUER)
|
||||
.withClaim("username", username)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
public String accessTokenBad512(String username) {
|
||||
Algorithm algorithm = Algorithm.HMAC512(SECRET); // $ HardcodedCredentialsApiCall
|
||||
|
||||
return JWT.create()
|
||||
.withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME))
|
||||
.withIssuer(ISSUER)
|
||||
.withClaim("username", username)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
// GOOD: Get secret from system configuration then sign a token
|
||||
public String accessTokenGood512(String username) {
|
||||
String tokenSecret = System.getenv("SECRET_KEY");
|
||||
Algorithm algorithm = Algorithm.HMAC512(tokenSecret);
|
||||
|
||||
return JWT.create()
|
||||
.withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME))
|
||||
.withIssuer(ISSUER)
|
||||
.withClaim("username", username)
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/amazon-aws-sdk-1.11.700:${testdir}/../../../../../stubs/azure-sdk-for-java:${testdir}/../../../../../stubs/shiro-core-1.4.0:${testdir}/../../../../../stubs/jsch-0.1.55:${testdir}/../../../../../stubs/ganymed-ssh-2-260:${testdir}/../../../../../stubs/apache-mina-sshd-2.8.0:${testdir}/../../../../../stubs/sshj-0.33.0:${testdir}/../../../../../stubs/j2ssh-1.5.5:${testdir}/../../../../../stubs/trilead-ssh2-212:${testdir}/../../../../../stubs/apache-commons-net-3.8.0:${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/mssql-jdbc-12.2.0
|
||||
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/amazon-aws-sdk-1.11.700:${testdir}/../../../../../stubs/azure-sdk-for-java:${testdir}/../../../../../stubs/shiro-core-1.4.0:${testdir}/../../../../../stubs/jsch-0.1.55:${testdir}/../../../../../stubs/ganymed-ssh-2-260:${testdir}/../../../../../stubs/apache-mina-sshd-2.8.0:${testdir}/../../../../../stubs/sshj-0.33.0:${testdir}/../../../../../stubs/j2ssh-1.5.5:${testdir}/../../../../../stubs/trilead-ssh2-212:${testdir}/../../../../../stubs/apache-commons-net-3.8.0:${testdir}/../../../../../stubs/mongodbClient:${testdir}/../../../../../stubs/mssql-jdbc-12.2.0:${testdir}/../../../../../stubs/auth0-jwt-2.3
|
||||
|
||||
Reference in New Issue
Block a user