HIGH security misconfigurationflaskbasic auth

Security Misconfiguration in Flask with Basic Auth

Security Misconfiguration in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability

Security misconfiguration in a Flask app using HTTP Basic Authentication commonly arises from a combination of default or weak settings and improper handling of credentials. When Basic Auth is enabled without enforcing HTTPS, credentials are transmitted in base64 encoding that is trivial to decode. A base64 string is not encryption; it offers no confidentiality. If TLS is missing or misconfigured (for example, allowing both HTTP and HTTPS or using self-signed certificates without strict verification), an attacker on the network can intercept credentials via passive sniffing or active downgrade attacks.

Another misconfiguration is verbose error messages that disclose stack traces or internal paths, which can help an attacker refine injection or enumeration. Flask’s default debug mode, if left enabled in production, exposes a debugger that can lead to remote code execution and reveals configuration details useful for further exploitation. Hardcoded credentials or storing passwords in plain text in source code or configuration files also constitute misconfiguration; this makes it easy for an attacker who gains filesystem access to recover credentials immediately.

Insecure defaults extend to how authentication is implemented. For example, using flask.request.authorization naively without validating and sanitizing the provided username and password can lead to injection issues when those values are later used in system commands or logs. Additionally, missing protections like account lockout or rate limiting on the login endpoint enables credential brute-forcing. Because Basic Auth sends credentials with every request, the attack surface is larger: any leaked token (for instance, via logs or browser history) can be reused until it expires. Misconfigured CORS can also expose authentication endpoints to unauthorized origins, further widening the risk. These issues are magnified when Basic Auth is used without additional controls, such as short token lifetimes or multi-factor mechanisms.

Basic Auth-Specific Remediation in Flask — concrete code fixes

To remediate misconfiguration, enforce HTTPS for all traffic and never transmit Basic Auth credentials over HTTP. Use strong hashing for stored passwords (e.g., bcrypt) rather than plain text, and avoid hardcoding credentials in source code. Apply robust error handling to prevent information leakage, and add rate limiting to mitigate brute-force attempts. Below are concrete, secure code examples for a Flask application using HTTP Basic Auth.

Enforce HTTPS and require secure transmission

Ensure your deployment terminates TLS and that Flask is configured to reject plain HTTP. In development, you can use an SSL context with self-signed certificates for testing only.

from flask import Flask, request, Response
import ssl

app = Flask(__name__)

# Enforce HTTPS in production by using a proper reverse proxy (e.g., Nginx, Traefik)
# and setting PREFERRED_URL_SCHEME to 'https' if behind a proxy that sets headers.
# This snippet is for illustration; do not rely on Flask alone for TLS enforcement.

@app.before_request
def enforce_https():
    if not request.is_secure:
        return "Use HTTPS", 403

Secure Basic Auth implementation with hashed credentials and error handling

Store credentials as salted hashes. Verify the provided password against the hash rather than comparing plaintext secrets.

from flask import Flask, request, Response
import bcrypt

app = Flask(__name__)

# Example stored hash for user 'alice' with password 'CorrectHorseBatteryStaple'
# Generated with: bcrypt.hashpw(b'CorrectHorseBatteryStaple', bcrypt.gensalt())
USERS = {
    "alice": b'$2b$12$KIXgUuKp8y6qJzZ9YVvE2uK7l8m9n0o1p2q3r4s5t6u7v8w9x0y1z2'
}

def verify_password(username, password):
    if username not in USERS:
        # Use a dummy hash to prevent timing attacks
        bcrypt.hashpw(b'dummy', bcrypt.gensalt())
        return False
    return bcrypt.checkpw(password.encode('utf-8'), USERS[username])

@app.route('/')
def index():
    auth = request.authorization
    if not auth or not verify_password(auth.username, auth.password):
        return Response(
            'Could not verify your access level.',
            401,
            {'WWW-Authenticate': 'Basic realm="Login Required"'}
        )
    return f'Authenticated as {auth.username}'

Add error handling and avoid information leakage

Ensure errors do not expose stack traces or paths. Use generic messages and log securely for further analysis.

import logging
from flask import Flask, request, Response

app = Flask(__name__)
app.config['PROPAGATE_EXCEPTIONS'] = False

# Configure structured logging for auth events (do not log passwords)
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')

@app.errorhandler(500)
def handle_500(e):
    app.logger.error('Server error', exc_info=True)
    return Response('Internal server error', status=500)

@app.route('/')
def index():
    auth = request.authorization
    if not auth:
        return Response('Auth required', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
    # Authentication logic here
    return 'OK'

Apply rate limiting to the authentication endpoint

Use a lightweight in-memory store or integrate with a shared store for production. This example uses a simple dictionary for demonstration; prefer Flask-Limiter or a Redis-backed store in production.

from flask import Flask, request, Response
from time import time

app = Flask(__name__)

# Simple rate limiter per IP for demonstration; use Redis or Flask-Limiter in production.
attempts = {}

def is_rate_limited(ip):
    now = time()
    window = 60  # seconds
    limit = 5    # max attempts per window
    attempts.setdefault(ip, [])
    attempts[ip] = [t for t in attempts[ip] if now - t < window]
    if len(attempts[ip]) >= limit:
        return True
    attempts[ip].append(now)
    return False

@app.route('/')
def index():
    if is_rate_limited(request.remote_addr):
        return Response('Too many requests', 429)
    auth = request.authorization
    if not auth or not verify_password(auth.username, auth.password):
        return Response('Bad credentials', 401)
    return 'OK'

Frequently Asked Questions

Does Basic Auth over HTTPS protect against replay attacks?
Transport-layer protections prevent eavesdropping, but Basic Auth credentials can still be replayed by an adversary who captures a valid request. Use additional protections such as short-lived tokens, nonce values, or prefer token-based authentication where feasible.
Should I store Basic Auth passwords as plain text in configuration files?
No. Store only salted, strong hashes (e.g., bcrypt). Plain-text credentials in configuration files increase the risk of credential exposure through misconfiguration or source code leaks.