Skip to main content

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.

jwt_no_alg.png

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”.

jwt_no_alg_changed.png

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."

jwt_no_alg_changed_validated.png

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

jwt_with_alg.png

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.

jwt_with_alg_changed.png

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"

jwt_with_alg_ver_failed.png

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

jwt_cracked_command.png
jwt_cracked_password.png

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.

jwt_cracked_altered.png

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"

jwt_cracked_altered_pass_val.png