Log Injection in Flask
How Log Injection Manifests in Flask
Log injection in Flask applications occurs when untrusted user input is written directly to log files without proper sanitization, allowing attackers to manipulate log content and potentially compromise security monitoring systems. Flask's logging infrastructure, built on Python's logging module, creates several specific attack vectors that developers must understand.
The most common Flask-specific scenario involves request data being logged through Flask's built-in request logging. When Flask applications use the default logging configuration or custom loggers that include request parameters, attackers can inject malicious content into log files. For example, a Flask route that logs user input directly:
from flask import Flask, request
app = Flask(__name__)
@app.route('/search')
def search():
query = request.args.get('q', '')
app.logger.info(f'Search query: {query}')
return f'Results for {query}'An attacker can exploit this by sending a request like /search?q=test%0AERROR:root:Critical%20system%20failure. The newline character (%0A) breaks the log format, causing the injected text to appear as a separate log entry at the ERROR level, potentially triggering false alerts or masking real issues.
Flask's error handling presents another vulnerability. When exceptions occur, Flask's default error handlers may log stack traces that include user-controlled data. Consider this Flask route:
@app.route('/upload')
def upload():
try:
file = request.files['file']
process_file(file)
except Exception as e:
app.logger.error(f'Upload failed: {e}')
return 'Upload failed', 500An attacker can craft file names or content that, when processed, generate log messages containing malicious content. If the file processing function logs user-controlled data without sanitization, it creates an injection point.
Flask's request context also introduces unique log injection opportunities. The request object contains various user-controlled attributes like headers, form data, and JSON payloads. When developers log these attributes without validation, attackers can manipulate log content. For instance:
@app.route('/api/data')
def get_data():
data = request.get_json()
app.logger.info(f'Received data: {data}')
return jsonify(data)JSON payloads can contain strings with newline characters, log format specifiers, or other special characters that break log formatting or inject malicious content.
Flask's blueprint system can compound these issues when logging is configured at the application level but blueprints handle user input. If a blueprint logs request data without understanding the broader logging context, it may inadvertently create injection vulnerabilities.
Another Flask-specific scenario involves logging configuration through environment variables or configuration files. If these configurations include user-controlled values, attackers might manipulate how logs are formatted or where they're written, leading to log injection or log forging attacks.
Flask-Specific Detection
Detecting log injection vulnerabilities in Flask applications requires both static analysis of code patterns and dynamic testing of logging behavior. middleBrick's API security scanner includes specific checks for Flask applications that can identify these vulnerabilities without requiring source code access.
middleBrick's scanner examines Flask applications by sending crafted requests that test for log injection vulnerabilities. The scanner looks for endpoints that might log user input and attempts to inject newline characters, log format specifiers, and other special characters. For Flask applications, the scanner specifically tests:
- Query parameters that might be logged in request handlers
- JSON payloads in API endpoints that log request data
- Form data in endpoints that process and log user input
- Header values that might be included in log messages
- Path parameters that are logged without sanitization
The scanner uses a black-box approach, sending requests with payloads designed to test Flask's logging behavior. For example, it might send a request containing newline characters and observe whether the log output shows unexpected formatting or injected content.
middleBrick's LLM/AI security capabilities also help detect log injection in Flask applications that use AI features. The scanner tests for system prompt leakage and prompt injection, which can be exacerbated when logs contain AI model interactions or user prompts.
For developers who want to manually test their Flask applications, middleBrick provides a CLI tool that can scan specific endpoints. The CLI allows you to test your Flask API's logging behavior by running:
middlebrick scan https://yourapp.com/api/endpointThe CLI returns a security score and detailed findings, including any log injection vulnerabilities discovered during the scan.
middleBrick's GitHub Action integration enables continuous monitoring of Flask applications in CI/CD pipelines. You can configure the action to scan your Flask API endpoints on every pull request, ensuring that log injection vulnerabilities are caught before deployment.
The scanner also checks for proper logging configuration in Flask applications. It verifies that log levels are appropriately set, that sensitive data isn't being logged, and that log rotation and retention policies are in place to prevent log injection from causing long-term security issues.
Flask-Specific Remediation
Remediating log injection vulnerabilities in Flask applications requires a combination of input sanitization, proper logging practices, and secure configuration. The following approaches specifically address Flask's logging patterns and architecture.
The most effective defense is input sanitization before logging. Flask developers should create utility functions that sanitize user input before it's written to logs. Here's a Flask-specific approach:
import re
from flask import request, escape
def sanitize_for_logging(input_data):
"""Sanitize user input for safe logging in Flask applications"""
if isinstance(input_data, str):
# Remove newlines and carriage returns
sanitized = re.sub(r'[
]', ' ', input_data)
# Escape special characters
sanitized = re.sub(r'[%]', '%%', sanitized)
# Limit length to prevent log flooding
return sanitized[:1000]
elif isinstance(input_data, dict):
return {k: sanitize_for_logging(v) for k, v in input_data.items()}
elif isinstance(input_data, (list, tuple)):
return [sanitize_for_logging(item) for item in input_data]
return input_data
@app.route('/search')
def search():
query = request.args.get('q', '')
sanitized_query = sanitize_for_logging(query)
app.logger.info(f'Search query: {sanitized_query}')
return f'Results for {sanitized_query}'Flask's built-in escape() function can also be used for HTML context, but for logging, custom sanitization is often more appropriate since log files aren't typically rendered as HTML.
Another Flask-specific remediation involves using structured logging with proper field separation. Instead of string interpolation, use logging's built-in field support:
import logging
from flask import request
# Configure structured logging
app.logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
handler.setFormatter(formatter)
app.logger.addHandler(handler)
@app.route('/api/data')
def get_data():
data = request.get_json()
# Use structured logging with separate fields
app.logger.info('Received data', extra={'data': data})
return jsonify(data)This approach prevents format string injection because the logging system treats the message and extra data as separate fields.
For Flask applications that use blueprints or extensions, centralize logging sanitization in a base class or decorator:
from functools import wraps
def sanitize_log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Sanitize request data before processing
if request.is_json:
sanitized_data = sanitize_for_logging(request.get_json())
request._sanitized_json = sanitized_data
return func(*args, **kwargs)
return wrapper
@app.route('/upload')
def upload():
try:
file = request.files['file']
process_file(file)
except Exception as e:
sanitized_error = sanitize_for_logging(str(e))
app.logger.error(f'Upload failed: {sanitized_error}')
return 'Upload failed', 500Flask's application factory pattern can also help manage logging configuration across different environments, ensuring consistent sanitization practices.
For applications that log sensitive data, implement log redaction using Flask's request context to identify and mask sensitive fields:
SENSITIVE_FIELDS = {'password', 'token', 'api_key', 'ssn'}
def redact_sensitive_data(data):
if isinstance(data, dict):
return {k: ('REDACTED' if k.lower() in SENSITIVE_FIELDS else redact_sensitive_data(v))
for k, v in data.items()}
return data
@app.route('/api/login')
def login():
credentials = request.get_json()
sanitized_credentials = redact_sensitive_data(credentials)
app.logger.info(f'Login attempt: {sanitized_credentials}')
return 'Login processed'These remediation strategies, combined with regular scanning using middleBrick's security tools, create a robust defense against log injection vulnerabilities in Flask applications.