HIGH bleichenbacher attackflaskbasic auth

Bleichenbacher Attack in Flask with Basic Auth

Bleichenbacher Attack in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability

A Bleichenbacher Attack is a cryptographic side-channel that exploits adaptive chosen-ciphertext decryption behavior. When Basic Auth is used in Flask, the attack surface shifts to how credentials are compared and how errors are exposed after decoding the Authorization header. A typical Basic Auth flow in Flask decodes a base64 string, splits on :, and compares the provided username and password against stored values. If the comparison is not constant-time and the server responds differently to malformed credentials versus valid-but-wrong credentials, an attacker can infer information about the expected value byte-by-byte.

Consider a Flask route that uses Basic Auth without protection against timing attacks:

from flask import Flask, request, abort
import base64

app = Flask(__name__)

VALID_USER = 'admin'
VALID_PASS = 'S3cr3tP@ss'

@app.route('/protected')
def protected():
    auth = request.headers.get('Authorization', '')
    if not auth.lower().startswith('basic '):
        abort(401, 'Missing Basic Auth')
    try:
        decoded = base64.b64decode(auth.split(' ', 1)[1]).decode('utf-8')
    except Exception:
        abort(400, 'Invalid base64')
    username, password = decoded.split(':', 1)
    if username == VALID_USER and password == VALID_PASS:
        return 'OK'
    abort(403, 'Invalid credentials')

This code is vulnerable because the equality checks username == VALID_USER and password == VALID_PASS are short-circuit string comparisons. An attacker can send many crafted Authorization headers and measure response times or error messages to gradually deduce the correct password. In a cloud environment where timing differences can be amplified by network variability, this becomes practical. Moreover, if the Flask app returns distinct error messages for malformed base64 versus authentication failures, it provides an oracle that makes the Bleichenbacher-style adaptive attack feasible. The combination of Basic Auth (which transmits credentials in easily reversible encoding) and non-constant-time validation creates a chain where each successful byte guess reduces uncertainty, enabling offline password recovery without ever triggering account lockout.

middleBrick scans detect such authentication timing and oracle issues under its Authentication and Input Validation checks, providing findings with severity and remediation guidance. This is important because Basic Auth should only be used over TLS and must be hardened against side-channel leaks; otherwise, even correctly configured credentials can be extracted through repeated interactions.

Basic Auth-Specific Remediation in Flask — concrete code fixes

Remediation focuses on two areas: eliminating timing leaks and avoiding informative error differentiation. Use a constant-time comparison for credentials and ensure that all failure paths return the same HTTP status and minimal, non-distinguishing messages. Hashing or otherwise avoiding direct password comparison helps, but the comparison routine itself must be constant-time to prevent byte-by-byte inference.

Here is a hardened Flask example that uses hmac.compare_digest for constant-time comparison and standardizes error handling:

from flask import Flask, request, abort
import base64
import hmac

app = Flask(__name__)

VALID_USER = 'admin'
VALID_PASS = 'S3cr3tP@ss'

@app.route('/protected')
def protected():
    auth = request.headers.get('Authorization', '')
    if not hmac.compare_digest(auth.lower(), 'basic '):
        return _unauth()
    try:
        decoded = base64.b64decode(auth.split(' ', 1)[1]).decode('utf-8')
    except Exception:
        return _unauth()
    parts = decoded.split(':', 1)
    if len(parts) != 2:
        return _unauth()
    username, password = parts
    if not hmac.compare_digest(username, VALID_USER) or not hmac.compare_digest(password, VALID_PASS):
        return _unauth()
    return 'OK'

def _unauth():
    # Always return the same status and body to avoid leaking information
    return 'Unauthorized', 401, {'WWW-Authenticate': 'Basic realm="API"'}

Key changes:

  • Use hmac.compare_digest for both the scheme check and credential comparison to prevent timing attacks.
  • Treat any malformed Authorization header the same as invalid credentials by returning a uniform 401 Unauthorized with a generic WWW-Authenticate header.
  • Avoid early exits with different status codes or error bodies; this removes the oracle that would enable adaptive guessing.

In production, consider moving Basic Auth into a proxy or load balancer that handles TLS and authentication, and enforce HTTPS strictly. Also rotate credentials regularly and avoid embedding secrets in source code. middleBrick’s GitHub Action can be added to CI/CD pipelines to fail builds if risk scores drop below your chosen threshold, helping catch regressions before deployment.

Frequently Asked Questions

Why does using the same HTTP status code for all failures help mitigate Bleichenbacher-style attacks in Flask?
Returning the same status code and minimal body for all authentication failures removes timing and content-based side channels. Without distinguishable responses, an attacker cannot use an oracle to confirm partial guesses, which is essential for adaptive cryptographic attacks on credentials.
Does using Basic Auth over HTTPS fully protect against Bleichenbacher attacks?
No. Transport-layer encryption prevents eavesdropping on the credentials, but it does not stop a server-side side-channel in the application code. If the comparison logic is not constant-time or if error handling leaks information, an attacker who can interact with the endpoint can still perform adaptive guessing regardless of TLS.