Web Application Penetration Testing (WAPT): JWT Vulnerabilities

Image generated by ChatGPT

Hello everyone, in this article we will look at the most common vulnerabilities involving misconfigured JWTs! πŸ•΅οΈβ€β™‚οΈπŸ›

First of all, what is a Json Web Token (JWT)? In brief, it is a compact, URL-safe means of representing claims between two parties. It is widely used for handling authentication and authorization in web applications.

Here is an example of JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJCeSBSZWZlcmVuY2UiLCJpYXQiOjE3MjE1NTcyODQsImV4cCI6MTc1MzA5MzI4NCwiYXVkIjoiYnlyZWZlcmVuY2UubmV0Iiwic3ViIjoiZ2l1c2VwcGUudG9zY2Fub0BieXJlZmVyZW5jZS5uZXQiLCJHaXZlbk5hbWUiOiJHaXVzZXBwZSIsIlN1cm5hbWUiOiJUb3NjYW5vIiwiRW1haWwiOiJnaXVzZXBwZS50b3NjYW5vQGJ5cmVmZXJlbmNlLm5ldCIsIlJvbGUiOiJhZG1pbiJ9.YVVl9GSDmCX4fA-evb8voPdKElQL77BtdJt9TJStbPU

Example of JSON Web Token

A JWT consists of three parts: Header (it usually contains the type of token and the signing algorithm), Payload (it contains the claims about an entity and additional data, such as the issuing and expiration time), and Signature (used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn’t changed along the way). These parts are encoded as Base64Url (not encrypted!!!) strings and concatenated with dots (.) as separators. For example, the previous JWT can be divided into:

  • Header: the characters before the first dot (.)
// BASE64URL ENCODED HEADER
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"


// BASE64URL DECODED HEADER
{
  "typ": "JWT",
  "alg": "HS256"
}

Header of a JWT

  • Payload: the characters between the first dot (.) and the second dot (.)
// BASE64URL ENCODED PAYLOAD
"eyJpc3MiOiJCeSBSZWZlcmVuY2UiLCJpYXQiOjE3MjE1NTcyODQsImV4cCI6MTc1MzA5MzI4NCwiYXVkIjoiYnlyZWZlcmVuY2UubmV0Iiwic3ViIjoiZ2l1c2VwcGUudG9zY2Fub0BieXJlZmVyZW5jZS5uZXQiLCJHaXZlbk5hbWUiOiJHaXVzZXBwZSIsIlN1cm5hbWUiOiJUb3NjYW5vIiwiRW1haWwiOiJnaXVzZXBwZS50b3NjYW5vQGJ5cmVmZXJlbmNlLm5ldCIsIlJvbGUiOiJhZG1pbiJ9"

// BASE64URL DECODED PAYLOAD
{
  "iss": "By Reference",
  "iat": 1721557284,
  "exp": 1753093284,
  "aud": "byreference.net",
  "sub": "giuseppe.toscano@byreference.net",
  "GivenName": "Giuseppe",
  "Surname": "Toscano",
  "Email": "giuseppe.toscano@byreference.net",
  "Role": "admin"
}

Payload of a JWT

  • Signature: the characters after the second dot (.)
// BASE64URL ENCODED SIGNATURE
"YVVl9GSDmCX4fA-evb8voPdKElQL77BtdJt9TJStbPU"

// SIGNATURE COMPUTATION BASED ON THE ALGORITHM DECLARED IN THE HEADER
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  "secret"
)

Signature of a JWT

JWT.io is an interesting web app which allows you to decode, verify and generate JWT. If you are interested, you can find it here.

How JWTs are Used in Web Applications

When the user logs in a web application, the server validates the credentials. If valid, the server issues a JWT signed with a secret key. This token is sent back to the user and stored on the client-side (typically in the browser storage or a cookie).

JWT Generation

Every subsequent request from the client to the server will include this JWT in the Authorization header. The server will then validate the token to authorize the request. Below there is an example of HTTP request containing a JWT:

GET /api/protected/resource HTTP/1.1
Host: example.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJCeSBSZWZlcmVuY2UiLCJpYXQiOjE3MjE1NTcyODQsImV4cCI6MTc1MzA5MzI4NCwiYXVkIjoiYnlyZWZlcmVuY2UubmV0Iiwic3ViIjoiZ2l1c2VwcGUudG9zY2Fub0BieXJlZmVyZW5jZS5uZXQiLCJHaXZlbk5hbWUiOiJHaXVzZXBwZSIsIlN1cm5hbWUiOiJUb3NjYW5vIiwiRW1haWwiOiJnaXVzZXBwZS50b3NjYW5vQGJ5cmVmZXJlbmNlLm5ldCIsIlJvbGUiOiJhZG1pbiJ9.YVVl9GSDmCX4fA-evb8voPdKElQL77BtdJt9TJStbPU
Content-Type: application/json

Request containing a JWT in the Authorization Header

From a security perspective, JWT relies heavily on the secret used to sign it. This secret must be protected and kept secure. Avoid using weaker algorithms in favor of stronger ones and always set a reasonable expiration time for tokens to limit the damage if a token is compromised.

The Most Common Weaknesses of JWTs

JWTs are extremely secure when properly generated, but potential misconfigurations can lead to serious consequences, including (but not limited to) unauthorized access, privilege escalation, and impersonation.

The techniques described below are intended solely for educational purposes and ethical penetration testing. They should never be used for malicious activities or unauthorized access.

Let's see the most common weaknesses that can be easily exploited by attackers:

  • Weak Secret Keys: The token is digitally signed with a short, predictable, or easily guessable secret key. Attackers can brute force or guess the key to create valid tokens.
  • Algorithm Confusion: The algorithm for digitally signing the token is not fixed on the back-end side, but it is read from the header. Attackers can change the algorithm to "none" or from a strong algorithm (like RS256) to a weak one (like HS256) to bypass verification.
  • No Token Expiration: The expiration time is missing or it is very long (e.g., more than one day). This is a huge problem because, in case of theft, stolen tokens can be used indefinitely or for a long time.
  • Lack of Signature Validation: The digital signature is not verified by the server. This allows attackers to manipulate the JWT without being detected.
  • Storing Tokens Insecurely: The JWT is stored in the local storage, session storage, or cookies without proper security measures (e.g., Secure and HttpOnly flags). The session tokens can be accessed via XSS or CSRF attacks.
  • Exposing Sensitive Data in Payload: The JWT payload contains sensitive information (e.g., password or PII). The JWT is not encrypted (only Base64Url encoded), so if the token is intercepted or accessed, sensitive data can be leaked.
  • Failure to Revoke Tokens: The server does not revoke tokens after logout. For this reason, compromised tokens remain valid until they expire.
  • Insecure Implementation of Libraries: The back-end server uses outdated or insecure JWT libraries. The vulnerabilities can be exploited by attackers to compromise the session token.

JWT_Tool

JWT_Tool is a Python utility designed for testing and exploiting JWTs. It is commonly used by security professionals and penetration testers to identify and exploit vulnerabilities in JWT implementations.

To install the most up-to-date version, you can clone the Github repository and install it through Python:

git clone https://github.com/ticarpi/jwt_tool
python3 -m pip install -r requirements.txt

JWT_Tool Setup

Below there are some examples of usage:

  • Converting the JWT into a human readable format
python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJCeSBSZWZlcmVuY2UiLCJpYXQiOjE3MjE1NTcyODQsImV4cCI6MTc1MzA5MzI4NCwiYXVkIjoiYnlyZWZlcmVuY2UubmV0Iiwic3ViIjoiZ2l1c2VwcGUudG9zY2Fub0BieXJlZmVyZW5jZS5uZXQiLCJHaXZlbk5hbWUiOiJHaXVzZXBwZSIsIlN1cm5hbWUiOiJUb3NjYW5vIiwiRW1haWwiOiJnaXVzZXBwZS50b3NjYW5vQGJ5cmVmZXJlbmNlLm5ldCIsIlJvbGUiOiJhZG1pbiJ9.YVVl9GSDmCX4fA-evb8voPdKElQL77BtdJt9TJStbPU

Decoding a JWT


        \   \        \         \          \                    \ 
   \__   |   |  \     |\__    __| \__    __|                    |
         |   |   \    |      |          |       \         \     |
         |        \   |      |          |    __  \     __  \    |
  \      |      _     |      |          |   |     |   |     |   |
   |     |     / \    |      |          |   |     |   |     |   |
\        |    /   \   |      |          |\        |\        |   |
 \______/ \__/     \__|   \__|      \__| \______/  \______/ \__|
 Version 2.2.5                \______|             @ticarpi      

Original JWT: 

=====================
Decoded Token Values:
=====================

Token header values:
[+] typ = "JWT"
[+] alg = "HS256"

Token payload values:
[+] iss = "By Reference"
[+] iat = 1721557284    ==> TIMESTAMP = 2024-07-21 12:21:24 (UTC)
[+] exp = 1753093284    ==> TIMESTAMP = 2025-07-21 12:21:24 (UTC)
[+] aud = "byreference.net"
[+] sub = "giuseppe.toscano@byreference.net"
[+] GivenName = "Giuseppe"
[+] Surname = "Toscano"
[+] Email = "giuseppe.toscano@byreference.net"
[+] Role = "admin"

Seen timestamps:
[*] iat was seen
[*] exp is later than iat by: 365 days, 0 hours, 0 mins

----------------------
JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore
----------------------

Decoded JWT

  • Cracking the secret with a dictionary file
python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJCeSBSZWZlcmVuY2UiLCJpYXQiOjE3MjE1NTcyODQsImV4cCI6MTc1MzA5MzI4NCwiYXVkIjoiYnlyZWZlcmVuY2UubmV0Iiwic3ViIjoiZ2l1c2VwcGUudG9zY2Fub0BieXJlZmVyZW5jZS5uZXQiLCJHaXZlbk5hbWUiOiJHaXVzZXBwZSIsIlN1cm5hbWUiOiJUb3NjYW5vIiwiRW1haWwiOiJnaXVzZXBwZS50b3NjYW5vQGJ5cmVmZXJlbmNlLm5ldCIsIlJvbGUiOiJhZG1pbiJ9.YVVl9GSDmCX4fA-evb8voPdKElQL77BtdJt9TJStbPU -C -d wordlist.txt

Cracking the secret of a JWT (with HMAC-SHA algorithm)

        \   \        \         \          \                    \ 
   \__   |   |  \     |\__    __| \__    __|                    |
         |   |   \    |      |          |       \         \     |
         |        \   |      |          |    __  \     __  \    |
  \      |      _     |      |          |   |     |   |     |   |
   |     |     / \    |      |          |   |     |   |     |   |
\        |    /   \   |      |          |\        |\        |   |
 \______/ \__/     \__|   \__|      \__| \______/  \______/ \__|
 Version 2.2.5                \______|             @ticarpi      

Original JWT: 

[+] secret is the CORRECT key!
You can tamper/fuzz the token contents (-T/-I) and sign it using:
python3 jwt_tool.py [options here] -S HS256 -p "secret"

Found Secret Key

  • Tampering a JWT
python3 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJCeSBSZWZlcmVuY2UiLCJpYXQiOjE3MjE1NTcyODQsImV4cCI6MTc1MzA5MzI4NCwiYXVkIjoiYnlyZWZlcmVuY2UubmV0Iiwic3ViIjoiZ2l1c2VwcGUudG9zY2Fub0BieXJlZmVyZW5jZS5uZXQiLCJHaXZlbk5hbWUiOiJHaXVzZXBwZSIsIlN1cm5hbWUiOiJUb3NjYW5vIiwiRW1haWwiOiJnaXVzZXBwZS50b3NjYW5vQGJ5cmVmZXJlbmNlLm5ldCIsIlJvbGUiOiJhZG1pbiJ9.YVVl9GSDmCX4fA-evb8voPdKElQL77BtdJt9TJStbPU -T -S hs256 -p "secret"

Tampering a JWT

        \   \        \         \          \                    \ 
   \__   |   |  \     |\__    __| \__    __|                    |
         |   |   \    |      |          |       \         \     |
         |        \   |      |          |    __  \     __  \    |
  \      |      _     |      |          |   |     |   |     |   |
   |     |     / \    |      |          |   |     |   |     |   |
\        |    /   \   |      |          |\        |\        |   |
 \______/ \__/     \__|   \__|      \__| \______/  \______/ \__|
 Version 2.2.5                \______|             @ticarpi      

Original JWT: 


====================================================================
This option allows you to tamper with the header, contents and 
signature of the JWT.
====================================================================

Token header values:
[1] typ = "JWT"
[2] alg = "HS256"
[3] *ADD A VALUE*
[4] *DELETE A VALUE*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 0

Token payload values:
[1] iss = "By Reference"
[2] iat = 1721557284    ==> TIMESTAMP = 2024-07-21 12:21:24 (UTC)
[3] exp = 1753093284    ==> TIMESTAMP = 2025-07-21 12:21:24 (UTC)
[4] aud = "byreference.net"
[5] sub = "giuseppe.toscano@byreference.net"
[6] GivenName = "Giuseppe"
[7] Surname = "Toscano"
[8] Email = "giuseppe.toscano@byreference.net"
[9] Role = "admin"
[10] *ADD A VALUE*
[11] *DELETE A VALUE*
[12] *UPDATE TIMESTAMPS*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 9

Current value of Role is: admin
Please enter new value and hit ENTER
> tester
[1] iss = "By Reference"
[2] iat = 1721557284    ==> TIMESTAMP = 2024-07-21 12:21:24 (UTC)
[3] exp = 1753093284    ==> TIMESTAMP = 2025-07-21 12:21:24 (UTC)
[4] aud = "byreference.net"
[5] sub = "giuseppe.toscano@byreference.net"
[6] GivenName = "Giuseppe"
[7] Surname = "Toscano"
[8] Email = "giuseppe.toscano@byreference.net"
[9] Role = "tester"
[10] *ADD A VALUE*
[11] *DELETE A VALUE*
[12] *UPDATE TIMESTAMPS*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 0
jwttool_6c67371cfa42ea50d93f667fc8e9b964 - Tampered token - HMAC Signing:
[+] eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJCeSBSZWZlcmVuY2UiLCJpYXQiOjE3MjE1NTcyODQsImV4cCI6MTc1MzA5MzI4NCwiYXVkIjoiYnlyZWZlcmVuY2UubmV0Iiwic3ViIjoiZ2l1c2VwcGUudG9zY2Fub0BieXJlZmVyZW5jZS5uZXQiLCJHaXZlbk5hbWUiOiJHaXVzZXBwZSIsIlN1cm5hbWUiOiJUb3NjYW5vIiwiRW1haWwiOiJnaXVzZXBwZS50b3NjYW5vQGJ5cmVmZXJlbmNlLm5ldCIsIlJvbGUiOiJ0ZXN0ZXIifQ.F4AyV-DzZ2x9w57PmNIcN56QwgP4hvYC5CZl8qY9SfI

Tampered Token

  • Running All Tests! mode to scan the JWT for known vulnerabilities
python3 jwt_tool.py -M at -t "{{PROTECTED ENDPOINT}}" -rh "Authorization: Bearer {{JWT}}" -cv "{{Canary Value}}"

All Tests! Mode

where:

    • {{PROTECTED ENDPOINT}} is an endpoint protected by the authorization mechanisms
    • {{JWT}} is the token to be scanned
    • {{Canary Value}} is a text string that appears in response for valid tokens (e.g. "Welcome user!")

Some interesting resources:


Do you think that JWTs are more secure than session cookies? Share your opinion below and join the conversation! We value your insights and experiences. Leave a comment and let’s discuss the pros and cons of JWTs together! 🀝

Share this article: