Log Injection in Fastapi with Basic Auth
Log Injection in Fastapi with Basic Auth — how this specific combination creates or exposes the vulnerability
Log Injection occurs when untrusted input is written directly into log files without sanitization, enabling log forging or log poisoning attacks. In Fastapi applications that use HTTP Basic Authentication, this risk is pronounced because credentials are often logged for audit or debugging purposes. When a Fastapi endpoint decodes the Authorization header and uses the extracted username or password directly in log messages, an attacker can inject newline characters or other control characters to manipulate the log structure.
Consider a Fastapi route that validates Basic Auth and then logs the username for monitoring. If the username contains a newline (\n) or carriage return (\r), the log entry can be split into multiple lines. This can obscure the original log context, bypass log-based detection mechanisms, or inject malicious log entries that appear to originate from a different user or event. For example, an attacker could craft a username like alice\n[CRITICAL] Suspicious activity detected, causing the log to show a fabricated critical event attributed to the legitimate user.
In Fastapi, Basic Auth is typically handled by extracting the header, decoding the base64-encoded username:password string, and validating credentials. If the application logs the decoded username or password without validation, it exposes credentials in plaintext within logs, which is a severe security and privacy concern. Combined with Log Injection, this can lead to credential leakage in centralized logging systems where logs are aggregated and retained for compliance or monitoring. Attackers who can influence log content may also attempt to inject structured payloads (e.g., JSON-like fragments) that could be misinterpreted by log parsers, potentially enabling log injection-based attacks such as log forging or injection into audit trails.
The impact is amplified when logs are used for security monitoring or automated response. A forged log entry could trigger false alerts or obscure real attacks, undermining incident response efforts. Because Basic Auth transmits credentials in every request (albeit base64-encoded, not encrypted), any logging of these credentials must be strictly controlled and sanitized. middleBrick detects scenarios where authentication inputs may be reflected in application outputs or logs, highlighting risks specific to patterns like Fastapi Basic Auth implementations that lack input validation before logging.
Basic Auth-Specific Remediation in Fastapi — concrete code fixes
To mitigate Log Injection in Fastapi with Basic Auth, you must sanitize any user-controlled data before it reaches logging statements. This includes the username and password extracted from the Authorization header. Below are concrete, secure coding patterns you can adopt.
1. Avoid logging credentials entirely
The safest approach is to never log raw credentials. Instead, log only a user identifier that has been validated and sanitized, and ensure no newlines or control characters are present.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import re
import logging
app = FastAPI()
security = HTTPBasic()
logger = logging.getLogger(__name__)
def sanitize_log_input(value: str) -> str:
# Remove newlines, carriage returns, and other control characters
return re.sub(r'[\r\n\t]', '', value)
async def get_current_user(credentials: HTTPBasicCredentials = Depends(security)):
# Perform your authentication logic here
if not (credentials.username and credentials.password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid credentials',
headers={'WWW-Authenticate': 'Basic'}
)
# Example validation (replace with your backend check)
if credentials.username != 'alice' or credentials.password != 'correct_password':
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid credentials',
headers={'WWW-Authenticate': 'Basic'}
)
# Sanitize before any logging or usage
safe_username = sanitize_log_input(credentials.username)
logger.info(f'User authenticated: {safe_username}')
return {'username': safe_username}
@app.get('/items/')
async def read_items(user: dict = Depends(get_current_user)):
return {'message': 'Hello, ' + user['username']}
2. Use structured logging with strict output formatting
If you must include contextual data in logs, use structured logging with predefined fields and enforce strict formatting to prevent injection. Ensure that any user-controlled string is escaped or validated to exclude line breaks.
import logging
import json
from fastapi import FastAPI, Depends
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
logger = logging.getLogger('structured')
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
def sanitize_for_json(value: str) -> str:
# Basic control character removal for JSON-safe strings
return ''.join(char for char in value if char.isprintable() or char in (' ', '\t'))
@app.post('/login/')
async def login(credentials: HTTPBasicCredentials = Depends(security)):
safe_user = sanitize_for_json(credentials.username)
safe_pass = '***' # Never log passwords
log_entry = json.dumps({
'event': 'login_attempt',
'username': safe_user,
'status': 'success'
})
logger.info(log_entry)
return {'ok': True}
3. Validate and reject suspicious input early
Implement validation that rejects usernames or passwords containing newline or carriage return characters before they are processed or logged. This prevents injection attempts at the boundary.
from pydantic import BaseModel, validator
import re
class LoginData(BaseModel):
username: str
password: str
@validator('username')
def validate_username(cls, v):
if re.search(r'[\r\n]', v):
raise ValueError('username must not contain control characters')
return v
# In your route, parse and validate before authentication
def authenticate_user(data: LoginData):
# Your auth logic here
pass