JSON Web Tokens (JWTs)
JSON Web Token (JWT) is a format for transmitting cryptographically secure data. JWTs are typically used by web applications as a stateless session token.
in JWT-based authentication, the session token is replaced by a JWT containing user information. After verifying the token's signature, the server can retrieve all user info from the JWT claims sent by the user.
JWTs consist of three main parts: a header, a payload, and a signature, which are base64-encoded and separated by dot . characters:
<Base64_Header>.<Base64_Payload>.<Base64_Signature>Header - contains metadata about the token itself, holding information that allows interpreting it such as the ecryption algorithm used to secure the JWT token
Payload - contains the actual data making up the token. This data comprises multiple standard (registered) claims or arbitrary, user-defined claims
Signature - computed based on the JWT's header, payload, and a secret signing key, using the algorithm specified in the header. The integrity of the JWT token is protected by the signature.
If any data within the header, payload, or signature itself is manipulated, the signature will no longer match the token, thus enabling the detection of manipulation.
Having knowledge of the secret signing key is required to compute a valid signature for a signed JWT.
Useful Tools and Resources
Attacking Signature Verification
The signature protects data within the JWT's payload. Without knowing the JWT's secret key, it's impossible to manipulate the token without invalidating it.
However, there are some misconfigurations in web applications that lead to improper signature verification, enabling us to manipulate the data within a JWT's payload.
Basic Misconfigurations
Case 1 - JWT signature verification is not in place
The first easy misconfiguration is when the web application does not check the JWT's integrity. If the web application is misconfigured to accept JWTs without verifying their signature, we can manipulate our JWT to escalate privileges or change our user data.
Case 2 - Signing algorithm set to None
Setting a manipulated JWT's algorithm to none implies that the JWT does not contain a signature, and the web application should accept it without computing one, which sometimes allows bypassing signature verification checks.
To forge a JWT with the none algorithm, we must set the alg-claim in the JWT's header to none
Note: Even if JWT does not contain a signature, the final . character is required.

Algorithm Confusion
Description
Algorithm confusion is a JWT attack that forces the web application to use a different algorithm to verify the JWT's signature than the one used to create it.
If a web application uses an asymmetric algorithm like RS256, it signs JWTs with a private key and verifies them using a public key. However, if an attacker crafts a JWT that claims to use a symmetric algorithm like HS256, the situation changes, as it uses the same key for both signing and verification.
If the application naively trusts the alg field in the token header, it will attempt to verify the token using HS256 with whatever key it already has, which in this case is the public key.
Because the public key is, by definition, public, an attacker can sign a forged HS256 token using that public key, and the application will incorrectly accept it as valid.
This vulnerability only exists if the application chooses the verification algorithm based on the token’s alg header instead of enforcing a fixed, expected algorithm.
To prevent this attack, the application must ignore the JWT’s alg header and always use the server-side configured algorithm (e.g., always RS256).
Performing the Attack
To execute the algorithm confusion attack, we need the public key used by the web application for signature verification, which you can get:
from the web application's certificate
from the web application's JKWS key set usually at
https://example.com/.well-known/jwks.jsonfrom the JWT itself
To gain the key from the JWT, get two valid JWTs from the web application (by logging in two times) and use rsa_sign2n
git clone https://github.com/silentsignal/rsa_sign2n
cd rsa_sign2n/standalone/
docker build . -t sig2n
docker run -it sig2n /bin/bash
python3 jwt_forgery.py <JWT1> <JWT2>The tool may output multiple public key candidates based on the two JWTs you provided.
To reduce the number of candidates, we can rerun it with different JWTs captured from the web application
Additionally, the tool automatically creates symmetric JWTs signed with the computed public key in different formats, which you can use to test for an algorithm confusion vulnerability.
If a token is accepted, you have confirmed the algorithm confusion vulnerability exists.
To forge a new token with edited claims, use the public key saved by the tool to a local file named filename_x509.pem within the docker container in CyberChef JWT Sign. Set the signing algorithm to HS256 and paste the public key into the Private/Secret key field.
Remember to add a newline (\n) at the end of the public key!!
Cracking the JWT Secret
After gaining access to a valid JWT, it is possible to attempt to brute-force the signing secret to obtain it. JWT supports three symmetric algorithms based on potentially guessable secrets: HS256, HS384, and HS512. If your token uses one of those algorithms, you might be able to crack its secret key.
Having access to the signing secret key means we can forge and sign any new valid token.
To crack a JWT you can either use hashcat or jwt_tool:
hashcat -m 16500 jwt.txt /path/to/wordlist.txt
python3 jwt_tool.py JWT -C -d /path/to/wordlist.txtSome wordlists other than rockyou.txt to crack JWTs are:
After finding the JWT's secret key, you can forge a new one using Cyberchef JWT Sign or jwt_tool
Exploiting JWT Standard Claims
Several standard claims might be leveraged by an attacker to exploit JWTs
jwk claim
If the web application is misconfigured to accept arbitrary keys provided in the jwk claim, you can forge a JWT, sign it with your private key, and then provide the corresponding public key in the jwk claim for the web application to verify the signature and accept the JWT.
To do that, first generate your keys using
openssl genpkey -algorithm RSA -out exploit_private.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in exploit_private.pem -out exploit_public.pemThen, you can manually sign the new JWT using Cyberchef, or use the following script to generate the JWT (note: edit your payload accordingly)
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from jose import jwk
import jwt
# JWT Payload
jwt_payload = {'parameter1': 'value1', 'parameter2': 'value2'}
# convert PEM to JWK
with open('exploit_public.pem', 'rb') as f:
public_key_pem = f.read()
public_key = serialization.load_pem_public_key(public_key_pem, backend=default_backend())
jwk_key = jwk.construct(public_key, algorithm='RS256')
jwk_dict = jwk_key.to_dict()
# forge JWT
with open('exploit_private.pem', 'rb') as f:
private_key_pem = f.read()
token = jwt.encode(jwt_payload, private_key_pem, algorithm='RS256', headers={'jwk': jwk_dict})
print(token)Then run:
pip3 install pyjwt cryptography python-jose
python3 exploit.pyjku claim
When a web application does not correctly check this claim, it can be exploited using a nearly identical process to the jwk claim: instead of embedding the key details into it, the attacker hosts the key details on their web server and sets the JWT's jku claim to the corresponding URL.
It is crucial that the kid in the JWT matches exactly that of the public key exposed on the server, so that the server uses the correct key in the JWK Set
Also, the jwk claim can be exploited for blind GET based SSRF attacks!
kid claim
Depending on how the server manages the value of the kid and checks for a corresponding key, injection vulnerabilities such as SQLi or Path traversal can occur.
If the kid allows a path traversal vulnerability, it is possible to arbitrarily edit a JWT by making the kid point to a file whose contents are known, then sign the JWT with a symmetric key whose value corresponds to the contents of this file.
The easiest idea is to redirect the kid claim's value to the /dev/null file: since this file is empty, the attacker can create a symmetric key whose value is an empty character string. Since the kid points to an empty value, the attacker can modify the JWT as he wishes and sign it with his empty symmetric key. Since the /dev/null file (and therefore the symmetric key) has an empty value, the JWT’s signature will be valid.