# JWT Algorithm Confusion via PyJWT `alg` Header Trust

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():
    device_token = request.headers.get('X-Device-Auth')
    if not device_token:
        return jsonify({'error': 'Missing device token'}), 401
    try:
        public_key = open('/etc/iot/device_rsa.pub').read()
        device_claims = jwt.decode(device_token, public_key, algorithms=['RS256', 'HS256'])
        device_id = device_claims.get('device_id')
        if device_id:
            return jsonify({'status': 'authorized', 'device': device_id, 'admin': device_claims.get('is_admin', False)})
    except jwt.InvalidTokenError:
        return jsonify({'error': 'Invalid token'}), 403
    return jsonify({'error': 'Authorization failed'}), 403
```

## Explanation

The code allows both RS256 (asymmetric) and HS256 (symmetric) algorithms in jwt.decode(). An attacker can craft a token with 'alg': 'HS256' in the header and sign it using the public key as the HMAC secret, bypassing RSA signature verification and gaining unauthorized access with elevated privileges.

## Remediation

The fix removes 'HS256' from the allowed algorithms list, restricting token verification to only 'RS256' (asymmetric). This prevents algorithm confusion attacks where an attacker could sign a forged token using the public key as an HMAC secret with HS256, since the decoder will now reject any token that doesn't use RS256.

## Secure Code
```python
import jwt
from flask import request, jsonify

def validate_iot_device_token():
    device_token = request.headers.get('X-Device-Auth')
    if not device_token:
        return jsonify({'error': 'Missing device token'}), 401
    try:
        public_key = open('/etc/iot/device_rsa.pub').read()
        device_claims = jwt.decode(device_token, public_key, algorithms=['RS256'])
        device_id = device_claims.get('device_id')
        if device_id:
            return jsonify({'status': 'authorized', 'device': device_id, 'admin': device_claims.get('is_admin', False)})
    except jwt.InvalidTokenError:
        return jsonify({'error': 'Invalid token'}), 403
    return jsonify({'error': 'Authorization failed'}), 403
```
