Padding Oracle in Flask with Basic Auth
Padding Oracle in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability
A padding oracle in Flask with HTTP Basic Auth arises when encrypted data (for example, a session token or an API key) is decrypted server-side without integrity verification, and the server exposes different behavior depending on whether padding is valid. Basic Auth supplies credentials in the Authorization header as a base64-encoded string, but does not itself protect the confidentiality or integrity of any subsequent encrypted payload your application might process.
Consider a Flask endpoint that receives an encrypted cookie or header, decrypts it using a block cipher (e.g., AES-CBC) with a server-side key, and then checks business logic based on the plaintext. If the implementation distinguishes between a padding error and other exceptions — for example, returning HTTP 400 for bad padding and HTTP 401 for invalid credentials — an attacker can perform an interactive padding oracle attack. By observing status codes or timing differences, the attacker can iteratively decrypt ciphertext without knowing the key, potentially recovering session tokens or other sensitive values. The presence of Basic Auth may also mislead developers into believing transport-layer authentication is sufficient, encouraging them to handle encryption/decryption in application code where padding validation is not implemented in constant time.
Flask itself does not introduce padding; the risk comes from how encrypted data is handled after Basic Auth provides identity context. Common vulnerable patterns include using custom decryption routines, non-constant-time padding checks, or mixing authentication and decryption responsibilities. For example, if a JWT-like token is encrypted with AES-CBC and the server uses Python’s Crypto.Cipher without proper integrity protection (e.g., an HMAC), an attacker can exploit the padding oracle to recover plaintext. Because the scan checks for Data Exposure and Input Validation, findings may highlight missing integrity checks and non-constant-time padding validation, which are amplified when encrypted data is processed in an endpoint that also relies on Basic Auth for credential transport.
OpenAPI/Swagger analysis helps by resolving $ref definitions and cross-referencing runtime behavior: if an endpoint definition describes securitySchemes of type http with scheme basic, but the implementation performs custom decryption with variable timing or distinguishable error paths, the scan can correlate this with insecure cryptographic practices. This does not imply the scanner fixes the issue; it identifies the intersection of authentication and encryption handling that may lead to padding oracle conditions.
Basic Auth-Specific Remediation in Flask — concrete code fixes
Remediation focuses on removing custom decryption from Flask endpoints, avoiding padding-differentiating errors, and using standard, well-audited mechanisms for confidentiality and integrity. Do not implement your own padding validation or AES-CBC without integrity. Instead, use authenticated encryption and let the framework handle credentials.
Example of a vulnerable pattern to avoid — custom decryption with potential padding oracle:
from flask import Flask, request, make_response
from Crypto.Cipher import AES
from base64 import b64decode
import os
app = Flask(__name__)
KEY = os.urandom(32) # example; key management is separate
def decrypt_pkcs7(data, key):
# This is illustrative and not safe for production use
cipher = AES.new(key, AES.MODE_CBC, iv=data[:16])
plaintext = cipher.decrypt(data[16:])
# Vulnerable: padding check may leak via timing or error message
if len(plaintext) % 16 != 0:
raise ValueError('Invalid padding')
# Strip padding naïvely
pad_len = plaintext[-1]
return plaintext[:-pad_len]
@app.route('/secure')
def secure():
auth = request.authorization
if not auth or auth.username != 'admin' or auth.password != 'secret':
return 'Unauthorized', 401
token = request.cookies.get('token')
if token is None:
return 'Forbidden', 403
try:
payload = decrypt_pkcs7(b64decode(token), KEY)
except Exception as e:
return str(e), 500 # Distinguishing errors can aid an oracle
return f'OK: {payload.decode()}'
Remediation using Flask with standard HTTP authentication and avoiding custom decryption in request handling — use HTTPS, built-in mechanisms, and authenticated encryption:
from flask import Flask, request, make_response
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
import base64
app = Flask(__name__)
# In practice, store keys securely and rotate; this is illustrative
AESGCM_KEY = AESGCM.generate_key(bit_length=256)
def verify_basic_auth(auth_header: str) -> bool:
# Compare credentials using a constant-time check where feasible
expected = 'YWRtaW46c2VjcmV0' # base64('admin:secret') for illustration
import hmac
return hmac.compare_digest(auth_header, expected)
@app.route('/secure')
def secure():
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Basic '):
return 'Unauthorized', 401
if not verify_basic_auth(auth_header):
return 'Unauthorized', 401
token = request.cookies.get('token')
if token is None:
return 'Forbidden', 403
try:
data = base64.urlsafe_b64decode(token + '==') # ensure padding
aesgcm = AESGCM(AESGCM_KEY)
nonce = data[:12]
ciphertext = data[12:]
payload = aesgcm.decrypt(nonce, ciphertext, associated_data=None)
except Exception:
# Do not distinguish padding or decryption failures from auth failures
return 'Forbidden', 403
return f'OK: {payload.decode()}'
Key remediation points:
- Use HTTPS to protect credentials in transit; Basic Auth transmits base64-encoded credentials, not encrypted.
- Prefer standard authentication libraries or frameworks instead of rolling your own crypto.
- If you must process encrypted data, use authenticated encryption (e.g., AES-GCM) and do not expose padding or decryption errors that vary by input.
- Apply constant-time comparisons for credentials and avoid branching on secret-dependent conditions that could be probed by an attacker.
- Leverage the middleBrick CLI to scan from terminal with
middlebrick scan <url>and the GitHub Action to add API security checks to your CI/CD pipeline, failing builds if risk scores drop below your chosen threshold. This helps catch insecure encryption handling before deployment.