A family of standards that define how to represent keys, encrypted, and signed messages as JSON.
- JSON Web Signature: RFC 7515
- JSON Web Encryption: RFC 7516
- JSON Web Key: RFC 7517
- JSON Web Algorithms: RFC 7518
- JSON Web Token: RFC 7519
- Examples: RFC 7520
This vulnerability is tested with
jws_aes.rejectsNoneAlgorithmAndMissingSignature
.
The compact serialization for JWS is:
BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)
Where the JWS Signature
input is defined as:
ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload))
Please note that in the compact serialization format, there's no such thing as
an unprotected header. All headers are protected by the accompanying signature.
In theory, this is secure. In practice, some libraries are set up in a way where
the developer's intention to validate a signature can be thwarted by a
maliciously modified payload. An attacker can take a valid payload, remove the
JWS Signature
component (the empty string is valid), replace JWS Protected Header
with a modified one that claims the algorithm is none
, and then mutate
the JWS Payload
as much as they want. If the library ignores the fact that the
developer provided a verification JWK, it might accidentally accept the
malicious payload.
It's important to understand that this isn't about the developer making a
mistake and asking for the library to validate a JWS without providing a key.
This is actually providing a key but having the library silently accept the
payload as valid even though it wasn't actually signed. The JWS spec never
should have included the none
option. Most libraries have either disabled
none
or fixed their APIs to ban it whenever keys were provided.
This vulnerability is tested with
jws_ec.rejectsSymmetryConfusionAttacks_aesKeyFromEcVerificationKeyBytes
.
With asymmetric crypto, the assumption is the attacker has access to your public verification key but does not have access to your private signing key. With symmetric crypto, the attacker doesn't have access to the shared secret signing/verification key. A problem arises when the developer uses the library to verify an asymmetric signature but the library relies on untrusted input to decide what type of verification to perform.
An attacker can create an arbitrary payload, obtain the public verification key
(it's public), take the bytes of the verification key and use it as a symmetric
HMAC key, produce a valid signature for their payload, and hand the malicious
payload to the unsuspecting server. If the library looks at the headers, sees
that the alg
field indicates symmetric signing, and then proceeds to interpret
the public verification key given to it by the developer as a secret symmetric
key, it'll incorrectly conclude that the JWS is valid.
Here's an example of a vulnerable library's API signature: boolean verify(String serializedJws, String verificationKey)
As you can see, the developer has no way
of telling the library that they want to verify an asymmetric signature. The key
they provide is "stringly typed". The library might inspect the String and
conclude that it looks like a public RSA key and do the right thing. However, it
might also look at the as-yet-unverified alg
field on the tampered payload,
see that the attacker says that it's an HMAC signature, and proceed to convert
the string into bytes and use it as a HMAC secret key. If the attacker
transformed the public verification key in the same way as the library just did,
the signature will verify correctly and the server will accept the malicious
payload.
This vulnerability is tested with jws_ec.rejectsAttackerProvidedEmbeddedJwk
.
The JWS RFC defines the JWS
header value jwk
. This header can be included in the serialized JWS that the
server is trying to verify. Its purpose is defined as: The "jwk" (JSON Web Key) Header Parameter is the public key that corresponds to the key used to digitally sign the JWS. This key is represented as a JSON Web Key [JWK]. Use of this Header Parameter is OPTIONAL.
In both asymmetric and symmetric crypto, choosing which verification key to use absolutely must be under your sole control. The attacker can simply generate their own keypair, use it to sign an arbitrary payload, and then send it to the server. Normally the server would reject the JWS as invalid since the signature didn't validate according to the verification key provided by the developer. However, if the developer's choice is ignored in favor of the attacker's then the whole thing falls apart.
It's essentially the same thing as being able to change the password on an account to whatever you want before attempting to login.
There are several examples
of this happening. One library even ignored the spec requiring that jwk
be a
public key; it accepted symmetric keys as well.
Similar issues exist with jku
, x5u
, and x5c
which simply point to the keys
that should be trusted.
This vulnerability is tested with jwe_ec.rejectsInvalidCurvePoint
.
This is a common vulnerability that can exist in any library that deals with ECDH. There's at least one example of this happening with a JW crypto library. However, it's a common problem even outside the context of JW crypto.
When using elliptic-curve Diffie-Hellman with the standard NIST-defined curves, it's crucial that the library reject attacker-controlled points that are not actually on the curve being used. If the library proceeds to use the invalid points, it will start leaking portions of its private key since the curve may only have a handful of points on it (instead of many like the secure curves have).
With this attack, the attacker only has to send a few payloads using each curve of small order until they get a "successful decryption" response. Once obtained, they try again with another small order curve. Once enough values are collected, they can use the Chinese Remainder Theorem to recover the secret key.
For example, if the secret key s
is 22,040 (way too small but this is just an
example), the attacker can send at most 179 JWEs to find out that s mod 179 = 23
. They can then send at most 2,447 JWEs to find out that s mod 2447 = 17
.
Using the theorem, they can efficiently derive the value of s
much quicker
than brute force.
For this attack to work, the server must use the same key multiple times, which
is exactly what ES256
(ECDSA using P-256 and SHA-256) does.
This type of problem is typically caused by issues with the lower-level crypto library and is something that Project Wycheproof checks for directly. However, this particular problem is so widespread that it's been included here as well.
This vulnerability is tested with jws_mixedSymmetryKeyset.rejectsValid
.
Most libraries work with single JSON Web Keys as well as JSON Web Keysets which can contain multiple keys. Typically a keyset will contain one key during the steady state and two keys during a period of rotation. The new key may be the exact same kind of key as the old or it might be similar (but stronger). Rarely, it may even contain a new kind of key (going from an RSA decryption key to an elliptic curve decryption key).
What should never happen however is changing symmetry types or mixing secret keys and public keys. You never want to rotate from an AES decryption key to an RSA decryption key. Yes both are secret but they have very different properties. This becomes obvious when considering the encryption keyset... It would need to contain a secret AES encryption key and a public RSA encryption key. There's just too much of a chance that that keyset will be mishandled and the AES key leaked.
This vulnerability is tested with jws_aes.rejectsValidJsonSerialization
and
jwe_aes.rejectsValidJsonSerialization
.
JWE and JWS can be serialized with compact serialization or JSON serialization. The latter has several issues and should be avoided since it's much harder to get right compared with compact serialization. An incomplete list of problems follows.
Both JWE and JWS JSON serialization support unprotected headers. Here, "unprotected" means the signature/MAC doesn't take their value into account and therefore everything about them is completely changeable by an attacker and the victim will have no way of detecting it. An attacker can:
- Change the values of every unprotected header
- Completely remove unprotected headers
- Add new unprotected headers even if the original didn't include any at all
Fortunately, the RFC does state that, "The Header Parameter names in the two
locations MUST be disjoint". This sounds like it prevents the case of an
unprotected header value taking precedence over a protected header value but
opportunity for bugs abound. What if the library forgot to compare headers in
canonical form -- "alg"
vs "ALG"
? Any library that asks its
non-cryptographer users to decide what does and doesn't deserve integrity
protections is a bad library. The right answer is to integrity-protect the
entire message and take the guesswork out of it.
JWS JSON serialization supports multiple signatures, each with their own protected headers. The API library authors that have chosen to support this don't always make it clear that when 2 of 3 signatures verified, the headers from the 1 that failed verification shouldn't be trusted.
JWE made a number of design choices that greatly facilitate chosen ciphertext attacks attacks against ciphertexts encrypted with RSA. Probably the single most devastating mistake was to include RSA encryption with PKCS #1 padding into the standard. At the time the RFC was written, chosen ciphertext attacks with PKCS #1 padding were well known. The large number of CVEs document that correct implementations of RSA PKCS #1 are difficult. RSA-OAEP had excellent support in underlying libraries. A library that only supported RSA PKCS #1 but not RSA-OAEP could reasonable be called outdated.
Some implementations of JWE have been analyzed in the paper DSMMS16. The authors found a number of libraries that were succeptible to chosen ciphertext attacks.
An example of a library that was recently susceptible to chosen ciphertext attacks was jose4j. If a padding error happened during decryption then the library would generate a random symmetric key of the expected size. If a modified ciphertext had correct PKCS #1 padding then the encoded message would be returned. With high probability this message had a size that is not a valid key size. Hence valid and invalid paddings resulted in distinguishable behavior: the exceptions thrown in the two cases were distinct. The library would attempt to decrypt the symmetric part of the ciphertext in one case and skip it in the other case, leading to a timing difference.
One observation here is that it is very difficult to fully remove all artefacts in Java by using the JCE interface. Wrong PKCS #1 paddings throw an exception, while valid paddings don't. Hence attacks using side channels such as the ones described in RGGSWY18 may still be possible.
Test vectors in wycheproof/testvectors/json_web_encryption_test.json can be used to test for this vulnerability. One drawback is that they are currently provider dependent.
Unfortunately there are more unlucky design choices in JWE. One is to put an "alg" field into the header of a ciphertext. The alg field defines which encryption algorithm has been used for the encryption. A well known cryptographic principle states that each key should be used for one purpose (rsp. algorithm) only.
Some key types allow multiple algorithms. The algorithm associated with a key can be specified using the "alg" field. For example if an RSA key has the format
{
"alg": "RSA-OAEP",
"use": "enc",
"n": "...",
"e": "...",
"d": "...",
"p": "...",
"q": "...",
"dp": "...",
"dq": "...",
"qi": "...",
"kid": "...",
"kty": "RSA"
}
and the ciphertext has a header such as
{"alg":"RSA1_5","enc":"..."}
then we expect that the ciphertext is rejected without trying any decryption. Otherwise bugs in the RSA1_5 implementation can be exploited even if even the receiver only intents to use RSA-OAEP.
RFC 7517 Section 4.4 declares the "alg" field in the key as optional. This appears to be a shortcoming of the RFC. At least some applications using JWE mitigate the danger that come with such a definition by adding additional restrictions on valid keys. Rejecting ambiguous keys is very reasonable. As a result Wycheproof does not include test vectors with ambiguous keys.
For example RFC 7520 gives test vectors for JWE and JWS. Some of the test vectors are included in the Wycheproof tests. Some test vectors in RFC 7520 do not include an algorithm in the key. Since we consider it very reasonable if an implementations would reject such keys, the test vectors were modified in Wycheproof to always include the "alg" field. Keys without an "alg" field may or may not be rejected.