Format String in Flask with Basic Auth
Format String in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability
A format string vulnerability occurs when user-controlled input is passed directly into a formatting function such as print, logging, or string interpolation without proper sanitization. In Flask, combining Basic Authentication with unchecked logging or error messaging can expose sensitive information or enable arbitrary memory read/write via format specifiers like %s, %x, or %n.
Consider a Flask route that extracts credentials from Basic Auth headers and logs them using Python’s logging module. If the log message uses old-style formatting and the username or password is controlled by an attacker, they can supply format characters to leak stack contents or corrupt memory. For example:
from flask import Flask, request, jsonify
import logging
import base64
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
@app.route('/profile')
def profile():
auth = request.headers.get('Authorization')
if auth and auth.startswith('Basic '):
encoded = auth.split(' ')[1]
decoded = base64.b64decode(encoded).decode('utf-8')
username, password = decoded.split(':', 1)
# Vulnerable: user-controlled input used in a format string
logging.info('User logged in: %s, Password: %s' % (username, password))
return jsonify({'status': 'ok'})
return jsonify({'error': 'Unauthorized'}), 401
An attacker sending a request with Authorization: Basic d3JlY246%x%x%x could cause the application to output stack memory addresses or sensitive data in logs or error messages. This may facilitate further exploitation, such as bypassing authentication or leaking cryptographic material. The combination of Basic Auth and format strings is particularly dangerous because credentials are often logged for auditing, and developers may inadvertently trust the logged values.
In addition to logging, format string bugs can appear in error handling or custom debug endpoints. If user input from authentication flows is reflected in responses without validation, attackers can chain format specifiers to read or write arbitrary memory locations. This can lead to information disclosure, denial of service, or even code execution depending on the runtime environment. Because Basic Authentication transmits credentials in every request, the attack surface remains persistent across sessions.
To mitigate these risks, always use safe string formatting methods and avoid logging raw credentials. Treat authentication data as untrusted input and apply the same rigorous validation as any other user-supplied parameter. Security-focused scanning can detect such patterns in unauthenticated assessments, highlighting dangerous logging practices and format usage in authentication flows.
Basic Auth-Specific Remediation in Flask — concrete code fixes
Remediation focuses on eliminating format string misuse and safely handling Basic Auth credentials. The primary goals are to avoid passing untrusted input into formatting functions and to prevent accidental exposure of secrets through logs or error messages.
Use modern string formatting with explicit type conversion and strict validation. Instead of concatenating or using % formatting, prefer str.format or f-strings with sanitized inputs. Even better, avoid logging credentials altogether. If logging is required, log only non-sensitive metadata such as request IDs or usernames that have been normalized and validated.
Here is a secure version of the previous example, which decodes credentials safely and avoids format string injection:
from flask import Flask, request, jsonify
import logging
import base64
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
@app.route('/profile')
def profile():
auth = request.headers.get('Authorization')
if auth and auth.startswith('Basic '):
try:
encoded = auth.split(' ')[1]
decoded = base64.b64decode(encoded).decode('utf-8')
username, password = decoded.split(':', 1)
# Safe: no direct formatting of sensitive values
logging.info('User login attempt, username: %s', username)
# Validate credentials against a secure store
if valid_credentials(username, password):
return jsonify({'status': 'ok'})
else:
return jsonify({'error': 'Invalid credentials'}), 401
except (ValueError, UnicodeDecodeError, IndexError):
return jsonify({'error': 'Malformed authorization header'}), 400
return jsonify({'error': 'Unauthorized'}), 401
def valid_credentials(username, password):
# Placeholder for secure verification logic
return username == 'admin' and password == 's3cureP@ss'
Additionally, configure your logging system to filter or redact sensitive fields. For example, use a custom logging filter to remove or mask password-like values before records are emitted. This reduces the risk that format string bugs in third-party libraries or application code can leak credentials through log aggregation systems.
For production deployments, consider moving away from Basic Authentication in favor of token-based mechanisms such as OAuth 2.0 or session cookies with secure flags. If Basic Auth must be used, enforce HTTPS to protect credentials in transit and apply strict input validation on all decoded values. MiddleBrick scans can verify that no format strings appear in authentication-related code paths and that logging practices align with security best practices.