Missing JWT signature check¶
ID: java/missing-jwt-signature-check
Kind: path-problem
Security severity: 7.8
Severity: error
Precision: high
Tags:
- security
- external/cwe/cwe-347
Query suites:
- java-code-scanning.qls
- java-security-extended.qls
- java-security-and-quality.qls
Click to see the query in the CodeQL repository
A JSON Web Token (JWT) consists of three parts: header, payload, and signature. The io.jsonwebtoken.jjwt
library is one of many libraries used for working with JWTs. It offers different methods for parsing tokens like parse
, parseClaimsJws
, and parsePlaintextJws
. The last two correctly verify that the JWT is properly signed. This is done by computing the signature of the combination of header and payload and comparing the locally computed signature with the signature part of the JWT.
Therefore it is necessary to provide the JwtParser
with a key that is used for signature validation. Unfortunately the parse
method accepts a JWT whose signature is empty although a signing key has been set for the parser. This means that an attacker can create arbitrary JWTs that will be accepted if this method is used.
Recommendation¶
Always verify the signature by using either the parseClaimsJws
and parsePlaintextJws
methods or by overriding the onPlaintextJws
or onClaimsJws
of JwtHandlerAdapter
.
Example¶
The following example shows four cases where a signing key is set for a parser. In the first ‘BAD’ case the parse
method is used, which will not validate the signature. The second ‘BAD’ case uses a JwtHandlerAdapter
where the onPlaintextJwt
method is overriden, so it will not validate the signature. The third and fourth ‘GOOD’ cases use parseClaimsJws
method or override the onPlaintextJws
method.
public void badJwt(String token) {
Jwts.parserBuilder()
.setSigningKey("someBase64EncodedKey").build()
.parse(token); // BAD: Does not verify the signature
}
public void badJwtHandler(String token) {
Jwts.parserBuilder()
.setSigningKey("someBase64EncodedKey").build()
.parse(plaintextJwt, new JwtHandlerAdapter<Jwt<Header, String>>() {
@Override
public Jwt<Header, String> onPlaintextJwt(Jwt<Header, String> jwt) {
return jwt;
}
}); // BAD: The handler is called on an unverified JWT
}
public void goodJwt(String token) {
Jwts.parserBuilder()
.setSigningKey("someBase64EncodedKey").build()
.parseClaimsJws(token) // GOOD: Verify the signature
.getBody();
}
public void goodJwtHandler(String token) {
Jwts.parserBuilder()
.setSigningKey("someBase64EncodedKey").build()
.parse(plaintextJwt, new JwtHandlerAdapter<Jws<String>>() {
@Override
public Jws<String> onPlaintextJws(Jws<String> jws) {
return jws;
}
}); // GOOD: The handler is called on a verified JWS
}
References¶
zofrex: How I Found An alg=none JWT Vulnerability in the NHS Contact Tracing App.
Common Weakness Enumeration: CWE-347.