Padding Oracle in Flask with Api Keys
Padding Oracle in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
A padding oracle in Flask arises when an application uses block cipher modes such as CBC without proper integrity protection and returns distinct errors for invalid padding versus invalid authentication. If API access is controlled only by API keys and those keys are used solely as shared secrets for encryption or HMAC without constant-time verification, an attacker can submit modified ciphertexts and observe differences in error responses to gradually decrypt data or forge valid messages.
Consider a Flask route that decrypts an API key–protected token to authorize a request:
from flask import Flask, request, abort
from Crypto.Cipher import AES
from base64 import b64decode
app = Flask(__name__)
def decrypt_token(token_b64, key: bytes) -> bytes:
iv = token_b64[:16]
ciphertext = token_b64[16:]
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher.decrypt_and_verify(ciphertext, token_b64[-16:]) # simplified
@app.route("/resource")
def get_resource():
api_key = request.headers.get("X-API-Key")
token = request.args.get("token")
if not api_key or not token:
abort(400, "missing parameters")
key = derive_key(api_key) # e.g., PBKDF2 using api_key as password
try:
payload = decrypt_token(token, key)
except ValueError:
abort(400, "invalid token")
# process payload ...
return {"data": "ok"}
In this setup, the API key influences the derived encryption key. If decrypt_and_verify raises distinct exceptions for bad padding versus bad authentication, an attacker who can observe HTTP status codes or error messages can perform a padding oracle attack. They iteratively modify ciphertext blocks and learn plaintext bytes without knowing the key, potentially exposing sensitive information embedded in the token (such as user identifiers or scopes). Even when API keys are required, if the cryptographic implementation leaks side channels, the presence of the API key does not prevent decryption or forgery.
Moreover, if unauthenticated endpoints or OpenAPI specs expose behavior that varies based on padding validity, middleBrick’s security checks—specifically the Input Validation and Encryption categories—may flag this as a risk. The combination of Flask, API keys for authorization, and non-constant-time decryption creates a scenario where confidentiality can be undermined despite the presence of an API key mechanism.
Api Keys-Specific Remediation in Flask — concrete code fixes
Remediation focuses on ensuring that API keys do not inadvertently feed a padding oracle. Use an authenticated encryption mode with built-in integrity, verify integrity before processing any padding, and ensure error handling is uniform. Below are concrete, safe patterns for Flask using API keys.
Use AES-GCM with API-key–derived keys. AES-GCM provides confidentiality and authenticity in one step, eliminating padding issues:
from flask import Flask, request, abort
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from base64 import b64encode, b64decode
import hashlib
def derive_key(api_key: str) -> bytes:
return hashlib.sha256(api_key.encode("utf-8")).digest() # 32 bytes for AES-256
app = Flask(__name__)
def encrypt_token(plaintext: bytes, key: bytes) -> str:
nonce = get_random_bytes(12)
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
return b64encode(nonce + tag + ciphertext).decode("utf-8")
def decrypt_token(token_b64: str, key: bytes) -> bytes:
data = b64decode(token_b64)
nonce, tag, ciphertext = data[:12], data[12:28], data[28:]
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
return cipher.decrypt_and_verify(ciphertext, tag)
@app.route("/resource")
def get_resource():
api_key = request.headers.get("X-API-Key")
token = request.args.get("token")
if not api_key or not token:
abort(400, "missing parameters")
key = derive_key(api_key)
try:
payload = decrypt_token(token, key)
except Exception:
# Use a generic error to avoid leaking padding/oracle details
abort(401, "invalid token")
# process payload ...
return {"data": "ok"}
If you must use CBC, adopt an HMAC-based MAC (Encrypt-then-MAC) and constant-time comparison. Never decrypt based on padding alone:
import hmac
import hashlib
from flask import Flask, request, abort
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from base64 import b64encode, b64decode
def constant_time_compare(a: bytes, b: bytes) -> bool:
return hmac.compare_digest(a, b)
app = Flask(__name__)
def encrypt_token_cbc(plaintext: bytes, key: bytes) -> str:
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
# PKCS7 pad manually
pad_len = 16 - (len(plaintext) % 16)
plaintext_padded = plaintext + bytes([pad_len] * pad_len)
ciphertext = cipher.encrypt(plaintext_padded)
mac = hmac.new(key, iv + ciphertext, hashlib.sha256).digest()
return b64encode(iv + ciphertext + mac).decode("utf-8")
def decrypt_token_cbc(token_b64: str, key: bytes) -> bytes:
data = b64decode(token_b64)
iv, ciphertext, received_mac = data[:16], data[16:-32], data[-32:]
expected_mac = hmac.new(key, iv + ciphertext, hashlib.sha256).digest()
if not constant_time_compare(expected_mac, received_mac):
raise ValueError("invalid mac")
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext_padded = cipher.decrypt(ciphertext)
# Constant-time unpadding is not trivial; better to use GCM
pad_len = plaintext_padded[-1]
if not (1 <= pad_len <= 16):
raise ValueError("invalid padding")
# Verify padding bytes in constant-time style where possible
if plaintext_padded[-pad_len:] != bytes([pad_len]) * pad_len:
raise ValueError("invalid padding")
return plaintext_padded[:-pad_len]
@app.route("/resource")
def get_resource():
api_key = request.headers.get("X-API-Key")
token = request.args.get("token")
if not api_key or not token:
abort(400, "missing parameters")
key = derive_key(api_key)
try:
payload = decrypt_token_cbc(token, key)
except Exception:
abort(401, "invalid token")
return {"data": "ok"}
These examples show how API keys can be safely incorporated: derive a strong key, prefer authenticated encryption, and ensure errors do not distinguish between padding and MAC/authentication failures. middleBrick’s scans can highlight missing integrity checks and weak cryptographic practices under the Encryption and Input Validation categories.