{"title":"CBC Bit-Flipping via Unauthenticated AES-CBC Encryption","language":"Python","severity":"Critical","cwe":"CWE-353","source_lines":[9],"flow_lines":[9,10,16,17,18],"sink_lines":[17],"vulnerable_code":"from Crypto.Cipher import AES\nfrom Crypto.Util.Padding import pad, unpad\nimport base64\n\ndef generate_iot_device_token(device_id, access_level):\n    key = b'IoTMaster2024Key'\n    iv = b'StaticIV12345678'\n    payload = f\"device={device_id}&role={access_level}\"\n    cipher = AES.new(key, AES.MODE_CBC, iv)\n    encrypted = cipher.encrypt(pad(payload.encode(), AES.block_size))\n    return base64.b64encode(encrypted).decode()\n\ndef validate_iot_token(token):\n    key = b'IoTMaster2024Key'\n    iv = b'StaticIV12345678'\n    cipher = AES.new(key, AES.MODE_CBC, iv)\n    decrypted = unpad(cipher.decrypt(base64.b64decode(token)), AES.block_size)\n    params = dict(x.split('=') for x in decrypted.decode().split('&'))\n    return params['role'] == 'admin'","explanation":"The code uses AES-CBC encryption without message authentication (HMAC/MAC), allowing attackers to perform bit-flipping attacks on the ciphertext. By flipping specific bits in the encrypted token, an attacker can modify the decrypted 'role' parameter from 'guest' to 'admin' without knowing the encryption key, bypassing access controls. The use of a static IV further worsens the attack surface by making the encryption deterministic and allowing precise targeting of the first plaintext block via direct IV manipulation.","remediation":"The fix adds HMAC-SHA256 authentication over the ciphertext (Encrypt-then-MAC) to prevent bit-flipping attacks. Before decryption, the token's HMAC is verified using a constant-time comparison via hmac.compare_digest, ensuring any tampered ciphertext is rejected immediately. Additionally, a random IV is generated per token instead of using a static IV, preventing deterministic encryption patterns. Encryption and HMAC keys are separated to follow proper cryptographic key separation principles.","secure_code":"from Crypto.Cipher import AES\nfrom Crypto.Util.Padding import pad, unpad\nfrom Crypto.Random import get_random_bytes\nimport base64\nimport hmac\nimport hashlib\n\nENCRYPTION_KEY = b'IoTMaster2024Key'\nHMAC_KEY = b'IoTHmacSecret_32'\n\ndef generate_iot_device_token(device_id, access_level):\n    iv = get_random_bytes(AES.block_size)\n    payload = f\"device={device_id}&role={access_level}\"\n    cipher = AES.new(ENCRYPTION_KEY, AES.MODE_CBC, iv)\n    encrypted = cipher.encrypt(pad(payload.encode(), AES.block_size))\n    token_data = iv + encrypted\n    mac = hmac.new(HMAC_KEY, token_data, hashlib.sha256).digest()\n    return base64.b64encode(mac + token_data).decode()\n\ndef validate_iot_token(token):\n    raw = base64.b64decode(token)\n    mac_received = raw[:32]\n    token_data = raw[32:]\n    mac_computed = hmac.new(HMAC_KEY, token_data, hashlib.sha256).digest()\n    if not hmac.compare_digest(mac_received, mac_computed):\n        raise ValueError(\"Token integrity check failed: invalid HMAC\")\n    iv = token_data[:AES.block_size]\n    encrypted = token_data[AES.block_size:]\n    cipher = AES.new(ENCRYPTION_KEY, AES.MODE_CBC, iv)\n    decrypted = unpad(cipher.decrypt(encrypted), AES.block_size)\n    params = dict(x.split('=') for x in decrypted.decode().split('&'))\n    return params['role'] == 'admin'"}