Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
public class WeakJwtSecretKey {
public void testJjwtSignature() throws Exception {
{
// BAD: jjwt with a weak secret key shorter than 32 bytes
String secretKeyString = "mysecret";
}

{
// GOOD: jjwt with a strong secret key longer than 32 bytes
String secretKeyString = "rsaftyqumeowxt123m5mop0682atkjlo57quizs49rghbv";
}

String token = Jwts.builder().setSubject("Joe")
.signWith(SignatureAlgorithm.HS256, secretKeyString)
.compact();

Jws parseClaimsJws = Jwts.parser().setSigningKey(secretKeyString)
.parseClaimsJws(token);
}

// BAD: jose4j with a weak key and key validation disabled
public void testWeakJose4jSignature() throws Exception {
String secretKeyString = "mysecret";

JwtClaims claims = new JwtClaims();
claims.setExpirationTimeMinutesInTheFuture(10);
claims.setSubject("Joe");

Key key = new HmacKey(secretKeyString.getBytes("UTF-8"));
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
jws.setKey(key);
jws.setDoKeyValidation(false); // relaxes the key length requirement
String jwt = jws.getCompactSerialization();

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setAllowedClockSkewInSeconds(30)
.setRequireSubject()
.setVerificationKey(key)
.setRelaxVerificationKeyValidation() // relaxes key length requirement
.build();
JwtClaims processedClaims = jwtConsumer.processToClaims(jwt);
}

// GOOD: jose4j with a strong key
public void testStrongJose4jSignature() throws Exception {
String secretKeyString = "rsaftyqumeowxt123m5mop0682atkjlo57quizs49rghbv";

JwtClaims claims = new JwtClaims();
claims.setExpirationTimeMinutesInTheFuture(10);
claims.setSubject("Joe");

Key key = new HmacKey(secretKeyString.getBytes("UTF-8"));
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
jws.setKey(key);
String jwt = jws.getCompactSerialization();

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setAllowedClockSkewInSeconds(30)
.setRequireSubject()
.setVerificationKey(key)
.build();
JwtClaims processedClaims = jwtConsumer.processToClaims(jwt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and
self-contained way for securely transmitting information between parties as a
JSON object. This information can be verified and trusted through a digital
signature. JWTs can be signed using a secret (with the HMAC algorithm) or a
public/private key pair using RSA or ECDSA.</p>
<p>JWT Best Practices require human-memorizable passwords MUST NOT be directly used
as the key to a keyed-MAC algorithm such as “HS256”. RFC 7518 recommends to use
a password that is as large as (or larger than) the derived key length in JSON
web algorithms. Common JWT signature algorithms are HS256, HS384, and HS512.</p>
<p>Popular JWT libraries offer a method to set signing key with a handy string argument
in addition to the method with a byte array argument taking the binary cryptographic
key. It is a common mistake that JWT users are confused by the method signature and
attempted to use raw password strings as the key argument, which is almost always
incorrect for cryptographic hashes and can produce insecure results.</p>
<p>This rule finds uses of signature algorithms with a weak key of shorter length.
Signature algorithms are vulnerable to brute force attack when a weak key of
shorter length is used.</p>
</overview>

<recommendation>
<p>The password to generate a signing key shall be as large as (or larger than)
the derived key length, which is 256 bits long at the minimum for the
algorithm HS256, and have sufficient entropy.</p>
</recommendation>

<example>
<p>The following example shows both 'BAD' and 'GOOD' implementations. In the 'BAD'
implementation, a key with insufficient entropy is used. In the 'GOOD' case,
a strong key is used.</p>
<sample src="WeakJwtSecretKey.java" />
</example>

<references>
<li>
IETF
<a href="https://tools.ietf.org/id/draft-ietf-oauth-jwt-bcp-02.html#rfc.section.3.5">JSON Web Token Best Current Practices - Ensure Cryptographic Keys have Sufficient Entropy</a>
</li>
<li>
IETF
<a href="https://datatracker.ietf.org/doc/html/rfc7518#section-8.8">Password Considerations</a>
</li>
<li>
Auth0
<a href="https://auth0.com/blog/brute-forcing-hs256-is-possible-the-importance-of-using-strong-keys-to-sign-jwts/">Brute Forcing HS256 is Possible: The Importance of Using Strong Keys in Signing JWTs</a>
</li>
<li>
JWT
<a href="https://jwt.io/introduction">Introduction to JSON Web Tokens</a>
</li>
</references>
</qhelp>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* @name Weak HMAC secret key used to sign JWT (Json Web Tokens)
* @description JWT requires a minimum of 32 bytes of full-entropy key to sign and verify
* JWT messages. Weak HMAC secrets are vulnerable to brute-force attacks.
* @kind path-problem
* @id java/weak-jwt-hmac-secret
* @tags security
* external/cwe/cwe-326
*/

import java
import semmle.code.java.dataflow.TaintTracking
import experimental.semmle.code.java.frameworks.Jjwt
import experimental.semmle.code.java.frameworks.Jose4j
import DataFlow::PathGraph

/**
* Weak HMAC key with a length shorter than 32 bytes or 43 characters in base64.
*/
class WeakSecretKey extends Expr {
WeakSecretKey() { this.(CompileTimeConstantExpr).getStringValue().length() < 43 }
}

/**
* A taint-tracking configuration for using a weak secret key in JWT signing.
*/
class InsecureJwtSigningFlowConfig extends TaintTracking::Configuration {
InsecureJwtSigningFlowConfig() { this = "WeakJwtSecureKey:InsecureJwtSigningFlowConfig" }

override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof WeakSecretKey }

override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
(
ma.getMethod() instanceof SetSigningKeyMethod
or
ma.getMethod() instanceof SetJwtVerificationKey and
exists(MethodAccess rma |
rma.getMethod() instanceof SetRelaxJwtKeyValidation and
(
DataFlow::localExprFlow(ma, rma.getQualifier()) or
DataFlow::localExprFlow(rma, ma.getQualifier())
)
)
) and
sink.asExpr() = ma.getArgument(0)
)
}

override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(
ConstructorCall cc // new HmacKey(secretKeyString.getBytes("UTF-8"))
|
cc.getConstructedType().getASupertype*().hasQualifiedName("java.security", "Key") and
pred.asExpr() = cc.getAnArgument() and
succ.asExpr() = cc
)
or
exists(
MethodAccess ma // md.digest(secretKeyString.getBytes())
|
ma.getMethod()
.getDeclaringType()
.getASupertype*()
.hasQualifiedName("java.security", "MessageDigest") and
pred.asExpr() = ma.getArgument(0) and
succ.asExpr() = ma
)
}
}

from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureJwtSigningFlowConfig config
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Insecure JWT signing configuration with $@.",
source.getNode(), "weak HMAC Key"
Original file line number Diff line number Diff line change
Expand Up @@ -11,67 +11,7 @@

import java
import semmle.code.java.dataflow.DataFlow

/** The interface `io.jsonwebtoken.JwtParser`. */
class TypeJwtParser extends Interface {
TypeJwtParser() { this.hasQualifiedName("io.jsonwebtoken", "JwtParser") }
}

/** The interface `io.jsonwebtoken.JwtParser` or a type derived from it. */
class TypeDerivedJwtParser extends RefType {
TypeDerivedJwtParser() { this.getASourceSupertype*() instanceof TypeJwtParser }
}

/** The interface `io.jsonwebtoken.JwtParserBuilder`. */
class TypeJwtParserBuilder extends Interface {
TypeJwtParserBuilder() { this.hasQualifiedName("io.jsonwebtoken", "JwtParserBuilder") }
}

/** The interface `io.jsonwebtoken.JwtHandler`. */
class TypeJwtHandler extends Interface {
TypeJwtHandler() { this.hasQualifiedName("io.jsonwebtoken", "JwtHandler") }
}

/** The class `io.jsonwebtoken.JwtHandlerAdapter`. */
class TypeJwtHandlerAdapter extends Class {
TypeJwtHandlerAdapter() { this.hasQualifiedName("io.jsonwebtoken", "JwtHandlerAdapter") }
}

/** The `parse(token, handler)` method defined in `JwtParser`. */
private class JwtParserParseHandlerMethod extends Method {
JwtParserParseHandlerMethod() {
this.hasName("parse") and
this.getDeclaringType() instanceof TypeJwtParser and
this.getNumberOfParameters() = 2
}
}

/** The `parse(token)`, `parseClaimsJwt(token)` and `parsePlaintextJwt(token)` methods defined in `JwtParser`. */
private class JwtParserInsecureParseMethod extends Method {
JwtParserInsecureParseMethod() {
this.hasName(["parse", "parseClaimsJwt", "parsePlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtParser
}
}

/** The `on(Claims|Plaintext)Jwt` methods defined in `JwtHandler`. */
private class JwtHandlerOnJwtMethod extends Method {
JwtHandlerOnJwtMethod() {
this.hasName(["onClaimsJwt", "onPlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtHandler
}
}

/** The `on(Claims|Plaintext)Jwt` methods defined in `JwtHandlerAdapter`. */
private class JwtHandlerAdapterOnJwtMethod extends Method {
JwtHandlerAdapterOnJwtMethod() {
this.hasName(["onClaimsJwt", "onPlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtHandlerAdapter
}
}
import experimental.semmle.code.java.frameworks.Jjwt

/**
* Holds if `parseHandlerExpr` is an insecure `JwtHandler`.
Expand Down
86 changes: 86 additions & 0 deletions java/ql/src/experimental/semmle/code/java/frameworks/Jjwt.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Provides classes for working with the jjwt framework.
*/

import java

/** The interface `io.jsonwebtoken.JwtParser`. */
class TypeJwtParser extends Interface {
TypeJwtParser() { this.hasQualifiedName("io.jsonwebtoken", "JwtParser") }
}

/** The interface `io.jsonwebtoken.JwtParser` or a type derived from it. */
class TypeDerivedJwtParser extends RefType {
TypeDerivedJwtParser() { this.getASourceSupertype*() instanceof TypeJwtParser }
}

/** The interface `io.jsonwebtoken.JwtParserBuilder`. */
class TypeJwtParserBuilder extends Interface {
TypeJwtParserBuilder() { this.hasQualifiedName("io.jsonwebtoken", "JwtParserBuilder") }
}

/** The interface `io.jsonwebtoken.JwtHandler`. */
class TypeJwtHandler extends Interface {
TypeJwtHandler() { this.hasQualifiedName("io.jsonwebtoken", "JwtHandler") }
}

/** The class `io.jsonwebtoken.JwtHandlerAdapter`. */
class TypeJwtHandlerAdapter extends Class {
TypeJwtHandlerAdapter() { this.hasQualifiedName("io.jsonwebtoken", "JwtHandlerAdapter") }
}

/** The `parse(token, handler)` method defined in `JwtParser`. */
class JwtParserParseHandlerMethod extends Method {
JwtParserParseHandlerMethod() {
this.hasName("parse") and
this.getDeclaringType() instanceof TypeJwtParser and
this.getNumberOfParameters() = 2
}
}

/** The `parse(token)`, `parseClaimsJwt(token)` and `parsePlaintextJwt(token)` methods defined in `JwtParser`. */
class JwtParserInsecureParseMethod extends Method {
JwtParserInsecureParseMethod() {
this.hasName(["parse", "parseClaimsJwt", "parsePlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtParser
}
}

/** The `on(Claims|Plaintext)Jwt` methods defined in `JwtHandler`. */
class JwtHandlerOnJwtMethod extends Method {
JwtHandlerOnJwtMethod() {
this.hasName(["onClaimsJwt", "onPlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtHandler
}
}

/** The `on(Claims|Plaintext)Jwt` methods defined in `JwtHandlerAdapter`. */
class JwtHandlerAdapterOnJwtMethod extends Method {
JwtHandlerAdapterOnJwtMethod() {
this.hasName(["onClaimsJwt", "onPlaintextJwt"]) and
this.getNumberOfParameters() = 1 and
this.getDeclaringType() instanceof TypeJwtHandlerAdapter
}
}

/** The interface `io.jsonwebtoken.JwtParserBuilder` or a type derived from it. */
class TypeDerivedJwtParserBuilder extends RefType {
TypeDerivedJwtParserBuilder() { this.getASourceSupertype*() instanceof TypeJwtParserBuilder }
}

/**
* The `setSigningKey(byte[] key)` and `setSigningKey(String base64EncodedKeyBytes)` methods
* defined in `JwtParser` or `JwtParserBuilder`.
*/
class SetSigningKeyMethod extends Method {
SetSigningKeyMethod() {
this.hasName("setSigningKey") and
this.getNumberOfParameters() = 1 and
(
this.getDeclaringType() instanceof TypeDerivedJwtParser or
this.getDeclaringType() instanceof TypeDerivedJwtParserBuilder
)
}
}
38 changes: 38 additions & 0 deletions java/ql/src/experimental/semmle/code/java/frameworks/Jose4j.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Provides classes for working with the jose4j framework.
*/

import java

/** The class `org.jose4j.jws.JsonWebSignature`. */
class TypeJsonWebSignature extends RefType {
TypeJsonWebSignature() { this.hasQualifiedName("org.jose4j.jws", "JsonWebSignature") }
}

/** The class `org.jose4j.jwt.consumer.JwtConsumer`. */
class TypeJwtConsumer extends RefType {
TypeJwtConsumer() { this.hasQualifiedName("org.jose4j.jwt.consumer", "JwtConsumer") }
}

/** The class `org.jose4j.jwt.consumer.JwtConsumerBuilder`. */
class TypeJwtConsumerBuilder extends RefType {
TypeJwtConsumerBuilder() {
this.hasQualifiedName("org.jose4j.jwt.consumer", "JwtConsumerBuilder")
}
}

/** The `setVerificationKey()` method defined in `JwtConsumerBuilder`. */
class SetJwtVerificationKey extends Method {
SetJwtVerificationKey() {
this.hasName("setVerificationKey") and
this.getDeclaringType() instanceof TypeJwtConsumerBuilder
}
}

/** The `setRelaxVerificationKeyValidation()` method defined in `JwtConsumerBuilder`. */
class SetRelaxJwtKeyValidation extends Method {
SetRelaxJwtKeyValidation() {
this.hasName("setRelaxVerificationKeyValidation") and
this.getDeclaringType() instanceof TypeJwtConsumerBuilder
}
}
Loading