{"title":"CBC Padding Oracle via Distinct Error Messages","language":"Python","severity":"High","cwe":"CWE-203","source_lines":[13],"flow_lines":[6,7,8,9,10,11,13,14],"sink_lines":[14,16],"vulnerable_code":"from Crypto.Cipher import AES\nfrom Crypto.Util.Padding import unpad\nimport base64\n\ndef retrieve_iot_sensor_config(encrypted_payload):\n    master_key = b'IoTDevice2024Key'\n    try:\n        cipher_data = base64.b64decode(encrypted_payload)\n        iv, ciphertext = cipher_data[:16], cipher_data[16:]\n        cipher = AES.new(master_key, AES.MODE_CBC, iv)\n        plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)\n        return {\"status\": \"success\", \"config\": plaintext.decode()}\n    except ValueError:\n        return {\"status\": \"error\", \"message\": \"Invalid padding detected in sensor configuration\"}\n    except Exception as e:\n        return {\"status\": \"error\", \"message\": \"Decryption failed - corrupted data\"}","explanation":"The function returns distinct error messages for padding failures (ValueError) versus general decryption failures (generic Exception), creating a padding oracle vulnerability. An attacker can submit modified ciphertexts and use the different error responses to decrypt data byte-by-byte without knowing the encryption key. The observable discrepancy between 'Invalid padding detected in sensor configuration' and 'Decryption failed - corrupted data' leaks information about internal decryption state, enabling systematic ciphertext manipulation to recover plaintext.","remediation":"The fix unifies all error handling into a single generic exception handler that returns an identical error message regardless of whether the failure was due to invalid padding, base64 decoding issues, or any other decryption error. This eliminates the padding oracle by ensuring an attacker cannot distinguish between padding failures and other types of errors. Additionally, input length validation is performed before decryption to reject obviously malformed inputs early. Defining the generic error response once also ensures future code changes cannot accidentally introduce response divergence.","secure_code":"from Crypto.Cipher import AES\nfrom Crypto.Util.Padding import unpad\nimport base64\nimport hmac\nimport hashlib\nimport os\n\ndef retrieve_iot_sensor_config(encrypted_payload):\n    master_key = b'IoTDevice2024Key'\n    generic_error = {\"status\": \"error\", \"message\": \"Failed to retrieve sensor configuration\"}\n    try:\n        cipher_data = base64.b64decode(encrypted_payload)\n        if len(cipher_data) < 32 or len(cipher_data) % 16 != 0:\n            return generic_error\n        iv, ciphertext = cipher_data[:16], cipher_data[16:]\n        cipher = AES.new(master_key, AES.MODE_CBC, iv)\n        plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)\n        return {\"status\": \"success\", \"config\": plaintext.decode()}\n    except Exception:\n        return generic_error"}