Skip to main content

Overview

Log Injection, or Improper Output Neutralization for Logs, occurs when an application writes unsanitized user-supplied data directly to a log file. An attacker can exploit this by crafting input that contains special characters, such as newline (\n or %0a) and carriage return (\r or %0d) characters (CRLF). By injecting these characters, an attacker can forge new log entries, making the logs unreliable or actively misleading. They might inject fake log lines to cover their tracks, mislead administrators, or inject malicious content (like HTML/JavaScript) if the logs are viewed in a web-based log viewer (leading to XSS). 📜✍️➡️🤥

Business Impact

Log Injection undermines the integrity and trustworthiness of your application logs:
  • Covering Tracks: Attackers can inject fake log entries that mimic normal activity or error messages, confusing administrators and hiding their real actions (e.g., injecting a fake “Login Successful for admin” line after a real failed attempt).
  • Misleading Forensics: During an incident investigation, fake log entries can send investigators down the wrong path, wasting time and resources.
  • Log Viewer XSS: If logs are viewed in a web-based utility, injected HTML/JavaScript (<script>alert(1)</script>) can execute in the administrator’s browser, leading to session hijacking or other attacks on the admin.
  • Denial of Service: Injecting large amounts of junk data can fill up log storage, potentially causing DoS.

Reference Details

CWE ID: CWE-117 OWASP Top 10 (2021): A09:2021 - Security Logging and Monitoring Failures Severity: Medium

Framework-Specific Analysis and Remediation

This vulnerability is independent of specific web frameworks but depends on how the logging framework or custom logging logic handles data. The core issue is trusting that user input is single-line and benign. Key Remediation Principles:
  1. Sanitize Log Data: Before logging any user-supplied string, sanitize it by removing or encoding newline (\n), carriage return (\r), and other control characters.
  2. Use Structured Logging: Use a logging format like JSON. The logging library will typically handle encoding the user input as a single string value within a JSON key, naturally preventing it from breaking the log entry’s structure.
  3. Output Encoding (Log Viewers): Ensure any web-based log viewing application properly HTML-encodes the log data before rendering it in the browser to prevent XSS.
  4. Avoid Logging Unnecessary Input: Don’t log user input verbatim unless necessary. Log identifiers or sanitized summaries instead.

  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

Using Python’s logging module and inserting raw user input (e.g., from request.GET, request.POST) directly into log messages.

Vulnerable Scenario 1: Unsanitized Username in Log

# views.py
import logging
logger = logging.getLogger(__name__)

def user_lookup(request):
    username = request.GET.get('username')
    # DANGEROUS: 'username' is logged directly.
    # Input: username = "bob%0aLogin successful for user: admin"
    # Log Output (example):
    # INFO: Attempting lookup for user: bob
    # Login successful for user: admin
    logger.info(f"Attempting lookup for user: {username}")
    
    user = User.objects.filter(username=username).first()
    if not user:
        # DANGEROUS: Also logging input here.
        logger.warning(f"User not found: {username}")
        return HttpResponse("User not found", status=404)
    # ... return user data ...

Vulnerable Scenario 2: Unsanitized URL/Path in Log

# middleware.py
import logging
logger = logging.getLogger(__name__)

def logging_middleware(get_response):
    def middleware(request):
        # DANGEROUS: request.path might contain CRLF if not properly
        # handled by the web server/proxy.
        # Input: GET /page%0aHTTP/1.1 200 OK%0d%0a...
        logger.info(f"Processing request for path: {request.path}")
        response = get_response(request)
        return response
    return middleware

Mitigation and Best Practices

  • Sanitize user input by removing newlines before logging: safe_username = username.replace('\n', '_').replace('\r', '_').
  • Use Structured Logging: Configure logging (e.g., with python-json-logger) to output JSON. The user input will be safely contained within a JSON string value.

Secure Code Example

# views.py (Secure - Sanitization)
import logging
logger = logging.getLogger(__name__)

def user_lookup_secure(request):
    username = request.GET.get('username', '') # Default to empty
    
    # SECURE: Sanitize input by replacing newlines before logging.
    safe_username = username.replace('\n', '_').replace('\r', '_')
    
    logger.info(f"Attempting lookup for user: '{safe_username}'") # Added quotes for clarity
    
    user = User.objects.filter(username=username).first()
    if not user:
        logger.warning(f"User not found: '{safe_username}'")
        return HttpResponse("User not found", status=404)
    # ...
# settings.py (Secure - Structured Logging with JSON)
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'json': {
            # SECURE: Use a JSON formatter
            '()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
            'format': '%(asctime)s %(levelname)s %(name)s %(message)s',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'json', # Use the JSON formatter
        },
    },
    'loggers': {
        'myapp': {
            'handlers': ['console'],
            'level': 'INFO',
        },
    },
}
# views.py (Usage with Structured Logging)
# logger.info("User lookup attempted", extra={'username': username})
# Output: {"asctime": "...", "levelname": "INFO", ..., "message": "User lookup attempted", "username": "bob\nLogin successful"}
# The newline is now safe data within the JSON, not a new log line.

Testing Strategy

Identify all log statements (logger.info, warn, error, etc.) that include user-controlled input (request.GET, POST, headers). Submit input containing URL-encoded newline characters (%0a, %0d). Check the raw log files (not a web viewer) to see if fake log entries were successfully injected. If logs are viewed in a web UI, also test with HTML/script payloads (<script>...).