Practical Approaches for Testing and Breaking JWT Authentication

- 8 mins

Introduction

JWT (JSON Web Token) is a popular authentication/authorization protocol. It integrates cryptographic signatures into JSON objects to verify the object’s integrity.

The approach of JWT is systematic and relatively simple. Several pieces of research covered the security aspects of JWT authentication.

Several tools were also previously developed. However, after assessing the quality of the public tools, I concluded that we need better tools to perform testing against JWT. I have developed a set of scripts I call “jwt-pwn” that aims to test JWT authentication with stability, simplicity, and efficiency in mind. The script set is straightforward, integrating directly with the JWT Python library.

This article will cover the background of JWT, wrong implementations, and practical approaches to testing and breaking JWT. It will also cover a section of constructive criticism, where I will discuss each known public tool for testing JWT and the issues I faced when I came to the idea of developing jwt-pwn.


Background

The following figure explains the fundamental approach to JWT generation.

Source: https://jwt.io

Three parts structure JWT:


Testing the Implementation

1. Brute-forcing secret keys

JSON web tokens are typically signed with a secret key. A secret key is simply a password or digital key. If we can find the secret key, we can generate valid tokens on behalf of the targe application.

This test is done for any token generated with a password; the attack does not apply to the key-based signing of the token.

The interesting point is that it’s an offline password attack, so no noise would be made to the target’s backend. We only require a valid JWT the backend provides when authentication or similar situations.

2. Signing a new token with the “none” algorithm

This test is relatively straightforward. We would decode the value of the JWT without validating the signature. We will generate a new token with the “none” algorithm.

After this, we will replace the original JWT in the authentication with the newly generated token. Suppose it’s accepted, and the same response of the original token is given. In that case, the authentication functionality arbitrarily accepts the “none” algorithm, where we explicitly “ask” the JWT validator on the backend not to validate the token.

If it works, replay attacks can be done. I’m referring to the “replay attacks” term by replaying a valid token to the authentication controller without focusing on the source of obtaining the token. In our case, it will be signing a new token valid for another user (or admin).

3. Changing the signing algorithm of the token (for fuzzing purposes)

Let’s say a backend returns a JSON Web Token signed with RSA256. The validation test would aim to check whether the JWT validator enforces RSA256 in this case.

We will decode the value of the original JWT. Then, we will generate a newly signed token with our secret key using HS256.

This test may only partially break the JWT authentication system in typical cases. However, it can be a good practice to test it against and inspect its response for fuzzing purposes.

4. Signing the asymmetrically-signed token to its symmetric algorithm match (when you have the original public key)

When a token is asymmetrically signed (e.g., RSA256), and you obtained the public key that is used for verifying the signature, then an attack that can be applied is to generate a new token with the same payload and with a symmetric signing algorithm (e.g., HS256), while using the public key as the password.

If it works, then this means that the validator is not allowing the signing algorithm for the JWT, and we can generate arbitrary tokens that the JWT validator will accept.


Public Tools

Several tools aim to assess JWT authentication. The tested tools for doing JWT assessments face different engineering issues.

jwt_tool - (https://github.com/ticarpi/jwt_tool)

jwt_tool does various tests for JWT. However, the custom implementation of parsing and generating JWT causes errors and false positives. Jwt_tool uses a custom decoding method and encoding Base64 instead of the default JWT library. Although the JWT protocol is public and documented, Base64 varies from one implementation to another. For instance, multiple Base64 encoded strings can represent the same value.

Note: Jwt_tool released a new update (as of October 2019) that should fix most of the issues encountered.

c-jwt-cracker - (https://github.com/brendan-rius/c-jwt-cracker)

C-jwt-cracker is a tool to brute-force the private key of JWT. Besides it uses its implementation of JWT, the Base64 library used by c-jwt-cracker is proven to be buggy and delivers invalid results.

Quoting from the c-jwt-cracker Github page:

“The base64 implementation I use (from Apple) is sometimes buggy because not every Base64 implementation is the same. So sometimes, decrypting of your Base64 token will only work partially, and thus you can find an incorrect secret to your token”.

jwt-cracker - https://github.com/lmammino/jwt-cracker

This tool is limited to a single signing algorithm (HS256), which makes it unable to operate if a different signing algorithm is provided.

Additional issue

Besides, all tools discussed in the experiment use a linear approach to crack the password.


Introducing jwt-pwn

The scripts take every test and approach to break JSON web tokens and apply them systematically. Everything is also done using the primary JWT library for Python, “pyjwt”. Pyjwt is heavily used in real-world applications. It’s stable, tested, and maintained.

Using Pyjwt instead of writing a custom library, as many did, is much better stability-wise. Some known tools give wrong results due to failing to parse JWT tokens correctly. Usage of Pyjwt would generate the same results the backend parser would get in most cases.

First script: jwt-cracker.py

This script performs brute-force attacks via a provided wordlist against a JSON web token.

I had two prototypes for the brute-forcing algorithm part.

First approach

1- Load all the wordlist in a queue

2- spawn a new thread once an available thread is available (a thread lock controls this part).

After analysis, I found this to be better, less as good as needed. The reason is: Besides the actual brute-forcing, the problems are:

1- There should be a thread constantly unloading from the queue

2- Checker for thread availability.

3- Locker for threads from being spawned until an available thread exists.

4- Have the queue unloaded linearly.

Many factors are involved in this part that approached the algorithm not the most efficient.

Second approach

This is the correct method.

1- Split the entire wordlist into smaller queues.

2- Spawn new threads and feed every queue to a unique thread.

3- The thread is treated as a “worker”, and all the functions are synchronized with a global checker that checks if other workers find the key.

4- While the key is not found, all threads test values in the assigned queue.

5- If the key is found, all threads are joined, and the application terminates.

6- The application terminates when all values in every child queue are tested.

Problem

After implementing this design in Python, The GIL (Global Interpreter Lock) in Python has caused the app to be relatively slower than needed.

Second Script: jwt-cracker-go

The script is a linear implementation of the jwt-cracker in Golang. It’s really fast compared to other tools. However, it uses a linear approach for password cracking and is reliable as it uses the official Golang library for JWT to validate tokens. This library should be the same one developers use in real-world environments.

I have also written another variant that uses goroutines, which should be faster. The code for the multi-threaded variant is available in the project repository.

Third script: jwt-decoder.py

This is a simple script that decodes the values of JSON web tokens. It should be handy for testing JWT.

Fourth script: jwt-mimicker.py

This script creates an unsigned token from the JSON web tokens. This script applies the “none” attack discussed previously.

Fifth script: jwt-key_based_token-to-hs256.py

This script creates a signed JWT from a key-based JWT. This applies to the attack mentioned previously.

The script works on every key-based signing algorithm, thanks to the powerful pyjwt library.


What to Do?

Penetration Testers and Security Researchers

Test your organization’s JWT implementation via jwt-pwn, and report any weaknesses identified.

Developers and Defenders

Jwt-pwn Homepage

https://github.com/mazen160/jwt-pwn

Mazin Ahmed

Mazin Ahmed

Thoughts of a hacker

rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora