Exposing the Dark Side of JSON Web Tokens (JWT)

·

9 min read

Exposing the Dark Side of JSON Web Tokens (JWT)

JSON Web Tokens (JWTs) have become the backbone of modern web authentication, promising secure identity and privilege transfer. JWTs are cryptographically protected tokens transferring identity and privilege information about a user or client. Encoded in JSON, they leverage either JSON Web Signature (JWS) or JSON Web Encryption (JWE) standards. While they improve upon older standards, several design flaws and complexities make JWTs susceptible to security vulnerabilities.

The Fundamental Problems with JWTs

Before diving into specific attacks, let's dissect the inherent issues with JSON Web Tokens and the Javascript Object Signing and Encryption (JOSE) standards:

  1. Algorithmic Flexibility: The "alg" parameter allows attackers to suggest cryptographic algorithms, creating a massive attack surface.

  2. Cryptographic Complexity: Instead of providing a curated, expert-selected set of cryptographic primitives, developers are burdened with choosing from numerous options, many of which could be inherently insecure.

  3. Diverse Security Properties: A single token can be protected through multiple mechanisms:

    • Message Authentication Code (MAC)

    • Signed tokens

    • Encrypted tokens (shared secret)

    • Encrypted tokens (public key)

    • The infamous "none" algorithm (no protection)

  4. Unnecessary Complexity: JOSE specifications attempt to support numerous obscure use cases, dramatically expanding the potential attack vectors.

Cryptographic Properties Comparison Table

Token TypeNeeds Secret to ReadNeeds Secret to Modify
"none"NoNo
Symmetric JWSNoYes
Asymmetric JWSNoYes
Symmetric JWEYesYes
Asymmetric JWEYesNo

Attack #1 : Sign/Encrypt Confusion

JWTs, or JSON Web Tokens, are widely used for secure communication and identity verification. However, their dual functionality as signed (JWS) or encrypted (JWE) tokens introduces a potential vulnerability: sign/encrypt confusion. This issue stems from libraries that support both forms but fail to enforce strict validation, allowing attackers to exploit implementation oversights.


Theory

JWTs can serve different purposes:

  • JWS (Signed): Protects token integrity but does not hide its contents.

  • JWE (Encrypted): Hides token contents but also ensures they are not tampered with.

This duality becomes problematic with asymmetric encryption (e.g., RSA):

  • The public key is used for encryption, and the private key is required for decryption.

  • Insecure implementations may treat encrypted tokens as valid if the private key matches, even if the encryption wasn’t intended for validation.

This allows attackers to forge tokens that bypass validation:

  1. Encrypt a malicious token using the application’s public key.

  2. The server decrypts the token with its private key and treats it as authentic.


Exploitation

Under certain configurations, the confusion between signed and encrypted tokens can lead to authentication bypasses:

  1. The application issues asymmetrically signed tokens (e.g., RS256).

  2. The library supports JWS and JWE tokens simultaneously.

  3. The validation logic uses the same key pair for signing and decrypting tokens.

Here’s an example of a vulnerable token validator:

from authlib.jose import jwt, JsonWebKey
import sys, json

with open('rsa-key.jwk', 'r') as keyfile:
    key = JsonWebKey.import_key(json.load(keyfile))

def validate(token):
    claims = jwt.decode(token, key)
    claims.validate()

Why is this code vulnerable?

  • The key file contains both public and private keys.

  • If an attacker encrypts a token using the public key, the private key will decrypt it during validation.


Exploitation Scenario

To execute this attack:

  1. Obtain the public key: Public keys are often accessible via OpenID Connect endpoints or other public resources. Even if not explicitly available, public keys for algorithms like RS256 can be derived from two legitimate token signatures.

  2. Forge a malicious token:

    • Create a JWE using the public key.

    • Craft a payload with modified claims (e.g., elevated privileges).

  3. Send the malicious token: The server decrypts it using the private key and accepts it as valid.

Example:

  1. A legitimate token issued by the application:

     eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.c1uwoOj...
    
  2. The attacker modifies the claims and encrypts the token with the public key:

     {
         "protected": "eyJhbGciOiJSUzI1NiJ9",
         "payload": "eyJ1c2VyIjoiaGVja2VyIn0",
         "signature": "fake-signature"
     }
    
  3. A JWE tool, such as Burp Suite's JWT Editor, is used to encrypt the token.


Affected Libraries

Several libraries were found to be vulnerable to this attack in older versions:

  • Authlib (before version 1.1.0) - CVE-2022-39174

  • JWCrypto (before version 1.4) - CVE-2022-3102

  • JWX (before version 0.12.0)


Attack #2 : Polyglot Token

JWTs rely on JSON for structure and parsing, making them susceptible to inconsistencies between parsers or differing interpretations of token formats. Polyglot tokens exploit these discrepancies to craft inputs that are valid for one parser but interpreted differently by another, leading to unauthorized claim manipulations and security bypasses.


Theory

JWTs can be serialized in multiple ways:

  1. Compact Serialization: A single string separated by dots (e.g., header.payload.signature).

  2. JSON Serialization: A JSON object with explicit fields for the header, payload, and signature.

While the JWT RFC specifies that compact serialization should be used, many libraries also support JSON serialization or delegate parsing to general-purpose libraries. This flexibility introduces the potential for parser inconsistencies:

  • One parser may interpret a token's format differently than another.

  • Attackers can create tokens that validate under one parser but behave maliciously when processed by another.

For example:

  • A JWS represented in JSON serialization may appear valid to one library but contain claims that bypass validation when interpreted in compact form.

Exploitation

Here’s an example of vulnerable code from the python-jwt library:

def verify_jwt(jwt, pub_key=None, allowed_algs=None, iat_skew=timedelta(), checks_optional=False, ignore_not_implemented=False):
    [...]
    header, claims, _ = jwt.split('.')
    parsed_header = json_decode(base64url_decode(header))
    [...]
    if pub_key:
        token = JWS()
        token.allowed_algs = allowed_algs
        token.deserialize(jwt, pub_key)
    elif 'none' not in allowed_algs:
        raise _JWTError('no key but none alg not allowed')
    parsed_claims = json_decode(base64url_decode(claims))
    [...]
    return parsed_header, parsed_claims

This code assumes that all tokens use compact serialization and splits the JWT string into its header, claims, and signature components. However, when presented with a token in JSON serialization, it fails to parse correctly, leading to potential exploitation.


Exploitation Scenario

  1. Legitimate Token: A compact serialized JWT might look like this:

     AAAA.BBBB.CCCC
    
    • AAAA: Base64-encoded header.

    • BBBB: Base64-encoded payload (claims).

    • CCCC: Base64-encoded signature.

  2. Malicious Token: An attacker creates a JSON-serialized equivalent:

     {
         "protected": "AAAA",
         "payload": "BBBB",
         "signature": "CCCC"
     }
    
  3. Modified Token: The attacker injects malicious claims:

     {
         "AAAA": ".XXXX.",
         "protected": "AAAA",
         "payload": "BBBB",
         "signature": "CCCC"
     }
    
    • The AAAA field is ignored by the library responsible for signature validation (e.g., jwcrypto), but the python-jwt parser interprets XXXX as claims when splitting the token on dots.
  4. Result: The attacker-controlled claims (XXXX) bypass signature validation and are returned as validated claims.


Affected Libraries

The python-jwt library was identified as vulnerable in versions before 3.3.4 (CVE-2022-39227). This flaw allowed attackers to exploit differences between compact and JSON serialization parsers.


Attack #3 : Billion Hashes Attack

The Billion Hashes Attack exploits password-based encryption (PBES2) in JWTs, specifically the parameter that governs how computationally expensive key derivation should be. By manipulating this parameter, attackers can overwhelm servers with excessive computations, leading to denial-of-service (DoS) attacks.


Theory

JWTs support PBES2 algorithms for password-based encryption. These algorithms use PBKDF2 (Password-Based Key Derivation Function 2) to derive encryption keys from passwords. PBKDF2 includes an iteration count parameter (p2c) that defines the number of hash computations performed during key derivation.

  • High iteration counts slow down the function to make brute-force attacks more difficult.

  • However, JWTs define this iteration count in the token header. If a server automatically processes tokens, attackers can supply an exaggerated p2c value, forcing the server to perform millions or billions of hash computations.

Key concerns:

  • Resource Exhaustion: The server validates the authentication tag only after performing the key derivation.

  • Automated Processing: Libraries that process JWE-wrapped JWTs without restrictions are particularly vulnerable.


Exploitation

Attackers craft a JWT with an inflated iteration count in the header:

{
    "alg": "PBES2-HS512+A256KW",
    "p2s": "8Q1SzinasR3xchYz6ZZcHA",
    "p2c": 2147483647,
    "enc": "A128CBC-HS256"
}
  • alg: Specifies the PBES2 algorithm.

  • p2s: A random salt value required for PBKDF2.

  • p2c: The iteration count set to the maximum value of a signed 32-bit integer (2,147,483,647).

  • enc: The encryption algorithm.

The payload, encrypted key, and other fields can contain arbitrary values, as long as they conform to expected lengths. A complete token might look like this:

goCopy codeeyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOiI4UTFT
emluYXNSM3hjaFl6NlpaY0hBIiwicDJjIjoyMTQ3NDgzNjQ3LCJlbmMiOiJBMTI
4Q0JDLUhTMjU2In0.YKbKLsEoyw_JoNvhtuHo9aaeRNSEhhAW2OVHcuF_HLqS0n
6hA_fgCA.VBiCzVHNoLiR3F4V82uoTQ.23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKx
jruHXYsTHAJLZ2nsnGIX86vMXqIi6IRsfywCRFzLxEcZBRnTvG3nhzPk0GDD7FM
yXhUHpDjEYCNA_XOmzg8yZR9oyjo6lTF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_
hkBsnuoqoM3dwejXBtIodN84PeqMb6asmas_dpSsz7H10fC5ni9xIz424givB1Y
LldF6exVmL93R3fOoOJbmk2GBQZL_SEGllv2cQsBgeprARsaQ7Bq99tT80coH8I
tBjgV08AtzXFFsx9qKvC982KLKdPQMTlVJKkqtV4Ru5LEVpBZXBnZrtViSOgyg6
AiuwaS-rCrcD_ePOGSuxvgtrokAKYPqmXUeRdjFJwafkYEkiuDCV9vWGAi1DH2x
TafhJwcmywIyzi4BqRpmdn_N-zl5tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmw
C4PTQ2izEm0TuSE8oMKdTw8V3kobXZ77ulMwDs4p.ALTKwxvAefeL-32NY7eTAQ

Exploitation Scenario

  1. Target Application: A web application processes JWTs with PBES2-based encryption.

  2. Crafting the Token:

    • The attacker creates a malicious token with an excessively high p2c value.

    • Tools like Burp Suite or custom scripts can generate the token.

  3. Impact:

    • The server spends significant resources computing hashes for the inflated p2c value.

    • Legitimate requests may time out, leading to a denial of service.


Affected Libraries

The following libraries were found vulnerable in older versions:

  • jose (before versions 1.28.1, 2.0.5, 3.20.3, and 4.9.1) - CVE-2022-36083

  • jose-jwt (before version 4.1)


Bonus Attack : Key Injection

Theory

JWTs support a rarely used and potentially dangerous feature: the jwk header parameter, defined in RFC 7515. This parameter can contain a public key corresponding to the private key that signed the JWT. The idea is that the validator can use this key to verify the signature, even if it doesn’t already know the key.

However, this introduces a severe flaw:

  • Validators might trust the jwk header blindly.

  • Attackers could inject their own public key in the jwk header and use it to validate a forged token, bypassing authentication.

This vulnerability is not theoretical. It has been exploited in the past:

  • A similar issue was found in a Cisco JOSE library where attackers could forge tokens by injecting a jwk header.

  • More recently, the Python library Authlib was found to have this vulnerability in its JWS API.

Exploitation

Exploitation of the key injection vulnerability is straightforward with existing tools:

  1. Modify the JWT to include a malicious jwk header containing the attacker’s public key.

  2. Sign the token with the corresponding private key.

  3. Tools like JSON Web Token Toolkit or Burp Suite's JWT Editor can automate this process.

Example of a malicious JWT with an embedded jwk:

{
    "alg": "RS256",
    "jwk": {
        "kty": "RSA",
        "kid": "attacker-key",
        "n": "public_key_modulus",
        "e": "AQAB"
    },
    "payload": "attacker_claims",
    "signature": "fake_signature"
}

Affected Libraries

  • Authlib (before version 1.1.0) - CVE-2022-39175

Conclusions and Recommendations

While JWTs have improved upon older cryptographic standards, their complexity and flexibility introduce significant risks. The attacks discussed here demonstrate how design flaws and implementation mistakes can compromise security.

Recommendations for Mitigation

For JWT/JOSE Library Developers:

  1. Simplify the Feature Set:

    • Avoid supporting unnecessary features.

    • Disable rarely used options, such as the jwk header or password-based encryption, by default.

  2. Enforce Validation Rules:

    • Reject tokens that rely on the alg parameter for deciding cryptographic algorithms.

    • Disallow non-compact serialization formats or ambiguous token representations.

  3. Use Delegation Safely:

    • When using external libraries for token validation, do not parse tokens separately. Trust only the validated payload returned by the library.

For Application Developers:

  1. Reevaluate JWT Usage:

    • For simple session management, plain random tokens might suffice. These are easier to revoke and don’t require cryptographic key management.
  2. Consider Alternatives:

    • Explore modern cryptographic tokens like PASETO, Macaroons, or Biscuits, which have stronger designs and simpler implementations.
  3. Explicitly Configure Validation Algorithms:

    • Avoid default settings. Explicitly define the algorithms and features your application supports.

For Standards Committees (JOSE Working Group):

  1. Provide Security Recommendations:

    • Define guidelines to help implementers avoid common pitfalls, such as those outlined here.
  2. Restrict Algorithms and Features:

    • Prohibit the use of risky features like password-based encryption or public-key encryption without signing.
  3. Discourage Untrusted alg Headers:

    • Validators should not rely on tokens to specify cryptographic validation methods.

Stay Vigilant | Stay Informed