{"title":"JWT Algorithm Confusion Attack via None Algorithm Bypass","language":"Python","severity":"Critical","cwe":"CWE-347","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"import jwt\ndef validate_iot_device_token(raw_token, device_registry):\n    unverified_hdr = jwt.get_unverified_header(raw_token)\n    algo = unverified_hdr.get('alg', 'HS256')\n    dev_secret = device_registry.get('signing_secret')\n    try:\n        decoded = jwt.decode(raw_token, dev_secret, algorithms=[algo])\n        dev_id = decoded.get('device_id')\n        role = decoded.get('role')\n        if role == 'admin' and dev_id in device_registry.get('trusted_devices', []):\n            return {'authorized': True, 'device': dev_id}\n        return {'authorized': False}\n    except jwt.ExpiredSignatureError:\n        return {'authorized': False, 'reason': 'expired'}\n    except Exception as e:\n        return {'authorized': False, 'reason': str(e)}","explanation":"The algorithm is extracted directly from the unverified JWT header (line 3) and stored in 'algo' (line 4), then passed as the sole allowed algorithm into jwt.decode() (line 7). This allows an attacker to forge a token with 'alg: none', remove the signature entirely, and bypass signature verification — granting unauthorized admin access without knowledge of the signing secret.","remediation":"The fix removes the extraction of the algorithm from the unverified JWT header and instead uses a server-side constant `ALLOWED_ALGORITHMS = ['HS256']` that is passed to `jwt.decode()`. This prevents an attacker from specifying 'none' or any other unexpected algorithm in the token header, ensuring signature verification always uses the intended cryptographic algorithm. Additionally, a check is added to ensure the signing secret is present before attempting validation.","secure_code":"import jwt\n\nALLOWED_ALGORITHMS = ['HS256']\n\ndef validate_iot_device_token(raw_token, device_registry):\n    dev_secret = device_registry.get('signing_secret')\n    if not dev_secret:\n        return {'authorized': False, 'reason': 'missing signing secret'}\n    try:\n        decoded = jwt.decode(raw_token, dev_secret, algorithms=ALLOWED_ALGORITHMS)\n        dev_id = decoded.get('device_id')\n        role = decoded.get('role')\n        if role == 'admin' and dev_id in device_registry.get('trusted_devices', []):\n            return {'authorized': True, 'device': dev_id}\n        return {'authorized': False}\n    except jwt.ExpiredSignatureError:\n        return {'authorized': False, 'reason': 'expired'}\n    except Exception as e:\n        return {'authorized': False, 'reason': str(e)}"}