Skip to main content

Overview

This vulnerability occurs when an application fails to properly validate cryptographic signatures or message authentication codes (MACs). This can happen in several ways:
  • Missing Verification: The application receives signed data but doesn’t check the signature at all.
  • Weak Algorithm Acceptance: The application accepts signatures using weak algorithms (like HMAC-SHA1) when stronger ones are expected.
  • Algorithm Confusion: The application trusts a parameter (often in the data itself, like a JWT header) that specifies which algorithm to use for verification, allowing an attacker to downgrade to a weak algorithm or even disable signature checks entirely (e.g., the JWT alg: none attack). ✍️❌

Business Impact

Failure to verify signatures properly allows attackers to tamper with data or impersonate legitimate senders.
  • Data Tampering: Signed messages, configurations, or tokens can be altered without detection.
  • Authentication Bypass: If signatures are used for authentication (like in JWTs), attackers can forge tokens to gain unauthorized access.
  • Impersonation: Attackers can craft messages that appear to come from a trusted source.

Reference Details

CWE ID: CWE-347 OWASP Top 10 (2021): A02:2021 - Cryptographic Failures Severity: High

Framework-Specific Analysis and Remediation

This is common with JSON Web Tokens (JWTs), SAML assertions, signed URLs, and any system where data integrity and authenticity rely on signatures. The key is to always verify the signature using a predetermined, strong algorithm configured on the server-side, and never trust algorithm information provided within the untrusted data itself.
  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

Using libraries like PyJWT but failing to verify the signature or accepting the 'none' algorithm.

Vulnerable Scenario 1: Decoding JWT without Verification

# auth/jwt_handler.py
import jwt

def decode_jwt_no_verify(token):
    try:
        # DANGEROUS: options={"verify_signature": False} disables signature check.
        # An attacker can forge any payload.
        payload = jwt.decode(token, options={"verify_signature": False})
        return payload
    except jwt.PyJWTError as e:
        print(f"JWT Error: {e}")
        return None

Vulnerable Scenario 2: Accepting ‘none’ Algorithm

Older versions of PyJWT might have allowed this, or custom logic might bypass checks if alg is none.
# auth/jwt_handler.py
def decode_jwt_accept_none(token, secret_key):
    try:
        header = jwt.get_unverified_header(token)
        alg = header.get('alg')

        if alg is None or alg.lower() == 'none':
            # DANGEROUS: Trusting the payload without signature if alg is 'none'.
            payload = jwt.decode(token, options={"verify_signature": False})
        else:
            # Verification happens here, but the 'none' path bypasses it.
            payload = jwt.decode(token, secret_key, algorithms=["HS256"])
        return payload
    except jwt.PyJWTError as e:
        print(f"JWT Error: {e}")
        return None

Mitigation and Best Practices

Always use jwt.decode() with the key and algorithms parameters specified. Never set options={"verify_signature": False}. Explicitly list the only acceptable strong algorithms (e.g., ["HS256", "RS256"]) in the algorithms parameter to prevent downgrade attacks.

Secure Code Example

# auth/jwt_handler.py (Secure)
import jwt
import os

# SECURE: Load the secret key from a secure source (e.g., environment variable)
SECRET_KEY = os.environ.get("JWT_SECRET_KEY")
# SECURE: Define the ONLY acceptable algorithms
ALLOWED_ALGORITHMS = ["HS256"]

def decode_jwt_secure(token):
    if not SECRET_KEY:
        raise ValueError("JWT Secret Key not configured")
    try:
        # SECURE: Verification is mandatory.
        # SECURE: Only allows algorithms listed in ALLOWED_ALGORITHMS.
        payload = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=ALLOWED_ALGORITHMS
        )
        return payload
    except jwt.ExpiredSignatureError:
        print("JWT Expired")
        return None
    except jwt.InvalidAlgorithmError:
        print("JWT Invalid Algorithm")
        return None
    except jwt.PyJWTError as e:
        print(f"JWT Invalid Token: {e}")
        return None

Testing Strategy

Write unit tests for your JWT decoding function.
  • Pass a token signed with the wrong key; assert decoding fails.
  • Pass a token with alg: none in the header; assert decoding fails.
  • Pass a token signed with an algorithm not in your ALLOWED_ALGORITHMS; assert decoding fails (InvalidAlgorithmError).
  • Pass a valid token; assert decoding succeeds.