Side Channel Attack in Flask with Basic Auth
Side Channel Attack in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability
A side channel attack in Flask using HTTP Basic Auth exploits timing and behavioral differences in how authentication logic executes, rather than breaking the cryptographic primitive itself. In Flask, when Basic Auth is implemented naively, the server may return different response times or statuses depending on whether the username exists and whether the password matches. An attacker can observe these differences through repeated requests, gradually inferring valid usernames and eventually recovering plaintext or hashed credentials.
Consider a typical implementation that retrieves a user record and compares the provided password with a stored hash. If the code returns early on a missing username, the server responds faster for non-existent users than for existing ones. An attacker can measure response latency and iteratively guess usernames until timing differences become statistically significant. This is a classic timing side channel because the execution path length depends on whether the username is present.
Even when a constant-time comparison is used for the password hash, other factors can leak information. For example, if Flask’s request.authorization parsing or the application’s error handling produces different HTTP status codes (e.g., 401 vs 404), an attacker gains actionable signals. Similarly, network jitter can mask timing differences, but in controlled environments—such as scanning with middleBrick’s unauthenticated checks—these deviations can be detected as part of its Authentication and BOLA/IDOR checks. middleBrick does not infer usernames directly; it surfaces inconsistent server behavior that indicates a side channel risk.
Another dimension involves observable server behavior after authentication. If a successful login changes rate limit headers, redirects, or resource availability, an attacker can correlate these changes with timing to confirm valid credentials. Because Basic Auth sends credentials in each request (base64-encoded, not encrypted), intercepted requests reveal the token; if the token is reused across insecure channels, the exposure is compounded. middleBrick’s checks include Rate Limiting and Encryption to highlight whether responses leak success/failure patterns or whether credentials are transmitted without adequate transport protections.
Insecure implementations may also leak information through logs, debugging output, or exception traces. For instance, a Flask route that logs the username on every request can provide an attacker with confirmation of valid accounts when logs are partially accessible. Similarly, verbose error messages can disclose stack traces or internal paths when authentication fails unexpectedly. middleBrick scans for Data Exposure and Input Validation issues, identifying endpoints that return overly descriptive errors or expose sensitive data in responses.
Finally, consider an endpoint that combines Basic Auth with additional authorization checks (e.g., object-level permissions). A subtle timing difference in permission evaluation—such as checking group membership after authentication—can compound the side channel. An attacker who first identifies valid users via timing can then probe for authorization inconsistencies. By running parallel checks, middleBrick tests Property Authorization and BOLA/IDOR to surface cases where authorization logic is not uniformly enforced across subjects.
Basic Auth-Specific Remediation in Flask — concrete code fixes
Remediation focuses on eliminating timing differences and ensuring consistent behavior regardless of authentication outcome. The key is to make the server execute the same code path and return the same status and timing for both valid and invalid credentials.
Example: Secure Flask route with Basic Auth
from flask import Flask, request, jsonify
import secrets
import bcrypt
from werkzeug.datastructures import Authorization
app = Flask(__name__)
# In-memory store for example; use a secure database in production
USERS = {
"alice": bcrypt.hashpw(b"correct horse battery staple", bcrypt.gensalt()),
"bob": bcrypt.hashpw(b("correct horse battery staple2"), bcrypt.gensalt()),
}
def verify_password(stored_hash, provided_password):
# Use constant-time comparison to avoid timing leaks
return bcrypt.checkpw(provided_password.encode("utf-8"), stored_hash)
@app.route("/secure")
def secure():
auth = request.authorization
if auth is None:
return unauthorized()
username = auth.username
password = auth.password
# Retrieve stored hash; if user does not exist, use a dummy hash
stored = USERS.get(username)
if stored is None:
# Use a dummy hash to keep execution time consistent
dummy_hash = bcrypt.hashpw(b"dummy", bcrypt.gensalt())
verify_password(dummy_hash, password)
return unauthorized()
if not verify_password(stored, password):
return unauthorized()
# Proceed only after consistent-time verification
return jsonify({"status": "ok", "user": username})
def unauthorized():
# Always return the same status and similar response shape
return jsonify({"error": "unauthorized"}), 401
if __name__ == "__main__":
app.run(debug=False)
This pattern ensures that:
- Whether the username exists, the function performs a hash verification with a dummy hash to keep timing uniform.
- The HTTP status code is consistently 401 for authentication failures, avoiding status-based leakage.
- No early returns that depend on username presence occur before the dummy verification.
Additional hardening practices
- Use HTTPS to protect credentials in transit; Basic Auth is not safe without TLS.
- Implement rate limiting at a layer independent of authentication results to prevent brute-force amplification of side channels.
- Avoid logging usernames or request details that could aid an attacker in correlating timing or status patterns.
- Prefer token-based authentication (e.g., JWT or session cookies with secure flags) to avoid sending credentials on every request.
middleBrick’s scans can validate these remediations by checking for consistent response codes, inspecting whether encryption is enforced, and analyzing authentication flows for timing anomalies. The CLI tool can be integrated into scripts to automate regression testing, while the Web Dashboard helps track improvements over time.