# JWT Algorithm Confusion Attack via Asymmetric-to-Symmetric Key Downgrade

Language: Python
Severity: Critical
CWE: CWE-327

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
import jwt
from flask import request, jsonify
def validate_iot_device_token():
    raw_token = request.headers.get('X-Device-Token')
    with open('/etc/iot/gateway_public.pem', 'r') as pem_file:
        rsa_pubkey = pem_file.read()
    try:
        hdr = jwt.get_unverified_header(raw_token)
        chosen_algo = hdr.get('alg', 'RS256')
        device_claims = jwt.decode(raw_token, rsa_pubkey, algorithms=[chosen_algo])
        return jsonify({'device_id': device_claims['did'], 'authorized': True})
    except jwt.ExpiredSignatureError:
        return jsonify({'error': 'token_expired'}), 401
    except Exception as exc:
        return jsonify({'error': str(exc)}), 403
```

## Explanation

The server reads the 'alg' field from the unverified JWT header (line 8) and passes it directly as the trusted algorithm to jwt.decode() (line 10). An attacker can craft a token with 'alg': 'HS256' in the header and sign it using the RSA public key (which is publicly known) as the HMAC-SHA256 secret. Since jwt.decode() is instructed to use HS256 and the RSA public key is used as the HMAC secret, the signature verification succeeds, completely bypassing RS256-based authentication.

## Remediation

The fix removes the extraction of the 'alg' field from the unverified JWT header and instead hardcodes the allowed algorithm list to ['RS256'] in the jwt.decode() call. This prevents an attacker from downgrading the algorithm to HS256 (or any other symmetric algorithm) and forging a valid token using the publicly known RSA public key as an HMAC secret.

## Secure Code
```python
import jwt
from flask import request, jsonify
def validate_iot_device_token():
    raw_token = request.headers.get('X-Device-Token')
    with open('/etc/iot/gateway_public.pem', 'r') as pem_file:
        rsa_pubkey = pem_file.read()
    try:
        device_claims = jwt.decode(raw_token, rsa_pubkey, algorithms=['RS256'])
        return jsonify({'device_id': device_claims['did'], 'authorized': True})
    except jwt.ExpiredSignatureError:
        return jsonify({'error': 'token_expired'}), 401
    except Exception as exc:
        return jsonify({'error': str(exc)}), 403
```
