Phishing Api Keys in Flask with Basic Auth
Phishing API Keys in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability
When an API key is embedded in a Flask application that uses HTTP Basic Authentication, the risk of phishing increases because the authentication flow and credential handling can expose secrets in multiple contexts. Basic Auth transmits credentials using a Base64-encoded string in the Authorization header, which is easily decoded if intercepted. In a Flask route that also exposes an API key—such as for third-party service access—developers sometimes store the key in environment variables and reference it directly in responses or logs. If a phishing page lures a user to a malicious site that triggers a cross-origin request to the Flask endpoint, the browser may send the Authorization header automatically if the site shares credentials or if CORS is permissive, potentially leaking credentials to an attacker who can capture them via a proxy or compromised browser extension.
Additionally, if the Flask application includes an endpoint that returns the API key in plaintext—perhaps for debugging or client-side consumption—an attacker crafting a convincing phishing page can trick a victim’s browser into requesting that endpoint. Because Basic Auth credentials are sent with every request to the protected origin, an attacker who controls a subdomain or a malicious site with a crafted form can perform a credentialed request using an img or fetch tag, logging the API key alongside the Basic Auth header. This becomes especially dangerous when the API key grants elevated permissions, such as publishing to a message queue or accessing sensitive data stores. Log files or error messages in Flask that include the API key—often due to verbose exception handling—can further aid an attacker in constructing convincing phishing lures that reference the exposed key to appear legitimate.
SSRF and insecure internal endpoints can compound the risk: a Flask service using Basic Auth may call an external API with an API key and inadvertently expose that key through open redirect or open proxy behavior. Attackers can then harvest keys via crafted URLs that appear to originate from a trusted Flask route. Because the scan checks for Data Exposure and Unsafe Consumption patterns, it can flag endpoints that return credentials or keys in responses, helping teams identify phishing-prone surfaces before attackers do.
Basic Auth-Specific Remediation in Flask — concrete code fixes
To reduce phishing risk when using Basic Auth in Flask, avoid embedding API keys in responses or logs, and ensure credentials are handled with strict transport security and minimal exposure. Use the werkzeug.security utilities to manage credentials safely and enforce HTTPS to prevent on-path interception of Base64-encoded headers.
Secure Flask Basic Auth with environment-backed API keys
from flask import Flask, request, jsonify, abort
from werkzeug.security import check_password_hash
import os
app = Flask(__name__)
# Example: user credentials stored as salted hashes in environment or a secure vault
USERS = {
"alice": os.getenv("ALICE_PW_HASH") # precomputed generate_password_hash("alicepass")
}
API_KEYS = {
"alice": os.getenv("ALICE_API_KEY")
}
def verify_auth(username, password):
if username in USERS and check_password_hash(USERS[username], password):
return True
return False
def require_auth(f):
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not verify_auth(auth.username, auth.password):
abort(401, description="Invalid credentials")
return f(auth.username, *args, **kwargs)
decorated.__name__ = f.__name__
return decorated
@app.route("/internal/data")
@require_auth
def get_data(auth_username):
# Do not include API keys in responses; fetch them server-side as needed
api_key = API_KEYS.get(auth_username)
# Use the key server-side to call another service; never echo it to the client
return jsonify({"status": "ok", "message": f"Request authorized for {auth_username}"})
if __name__ == "__main__":
# Enforce TLS in production; debug mode off
app.run(ssl_context="adhoc")
Avoid returning keys and tighten CORS
from flask import Flask, request
import os
app = Flask(__name__)
@app.route("/debug/keys")
def debug_keys():
# Never expose API keys in responses; remove such endpoints in production
abort(404, description="Endpoint not available")
# Configure CORS to be restrictive; avoid wildcard origins
from flask_cors import CORS
CORS(app, resources={r"/api/*": {"origins": ["https://trusted.example.com"]}})
Operational practices
- Use short-lived tokens or session cookies instead of long-lived API keys where possible.
- Rotate API keys regularly and monitor usage for anomalies.
- Ensure Flask’s
PROPAGATE_EXCEPTIONSis not set in production to prevent sensitive data in error pages. - Employ a WSGI server and TLS termination at the edge; never serve Basic Auth over HTTP.
By combining secure credential verification with disciplined key management and avoiding key disclosure in responses, you reduce the attack surface available to phishing attempts that rely on intercepted or leaked credentials.