HIGH command injectionflaskbasic auth

Command Injection in Flask with Basic Auth

Command Injection in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability

Command Injection occurs when untrusted input is passed to a system shell or command interpreter without proper validation or escaping. In a Flask application that uses HTTP Basic Authentication, the combination of credential handling, dynamic parameter usage, and subprocess invocation can create conditions where an authenticated (or unauthenticated, depending on implementation) attacker is able to inject and execute arbitrary commands.

Flask does not provide built-in protection against command injection; it relies on the developer to sanitize and validate any external input. When endpoints that accept user-controlled data—such as filenames, IP addresses, or query parameters—are used in functions like os.system, subprocess.run, or os.popen, the risk emerges. If Basic Auth is implemented naively, for example using a hardcoded check or a simple token comparison, an attacker may manipulate authentication headers or parameters to influence command construction. Moreover, if error messages or logs inadvertently reveal command execution details, they can aid further exploitation.

The vulnerability is not inherent to Basic Auth itself, but to how input flows through the application after authentication. For instance, an endpoint that accepts a hostname and passes it to a ping command can become an injection vector if the input is not properly constrained. Attack patterns such as shell metacharacters (;, &, |, `, $( )) enable command chaining or redirection, potentially bypassing intended logic and executing privileged operations.

Basic Auth-Specific Remediation in Flask — concrete code fixes

Securing Flask endpoints that use Basic Auth and external commands requires strict input validation, avoiding shell interpretation, and safe credential handling. Below are concrete remediation steps and code examples.

1. Avoid the shell for external commands

Use subprocess.run with a list of arguments and shell=False (the default) to prevent the shell from interpreting metacharacters. Never build command strings by concatenating user input.

import subprocess
from flask import Flask, request, jsonify

app = Flask(__name__)

# Safe: arguments passed as a list, no shell involved
def safe_ping(hostname):
    result = subprocess.run(['ping', '-c', '1', hostname], capture_output=True, text=True)
    return result.stdout, result.stderr

@app.route('/ping', methods=['GET'])
def ping():
    host = request.args.get('host', '')
    # Basic input validation: allow only alphanumeric, dash, and dot
    if not all(c.isalnum() or c in ('-', '.') for c in host):
        return jsonify({'error': 'Invalid host parameter'}), 400
    stdout, stderr = safe_ping(host)
    return jsonify({'output': stdout, 'error': stderr})

2. Secure Basic Authentication implementation

Implement Basic Auth without leaking credentials and with constant-time comparison where applicable. Use environment variables for secrets and avoid hardcoded passwords.

import os
import base64
import secrets
from flask import Flask, request, Response, jsonify

app = Flask(__name__)

# Example: retrieve credentials from environment
VALID_USER = os.environ.get('API_USER', 'admin')
VALID_PASS = os.environ.get('API_PASS')

def check_auth(username, password):
    # Use secrets.compare_digest to avoid timing attacks
    return secrets.compare_digest(username, VALID_USER) and secrets.compare_digest(password, VALID_PASS)

def authenticate():
    return Response(
        'Authentication required', 401,
        {'WWW-Authenticate': 'Basic realm="API"'}
    )

@app.before_request
def require_auth():
    if request.endpoint in ['login', 'static']:
        return
    auth = request.authorization
    if not auth or not check_auth(auth.username, auth.password):
        return authenticate()

@app.route('/login', methods=['GET'])
def login():
    return jsonify({'status': 'authenticated'})

3. Input validation and allowlisting

Define strict allowlists for inputs that affect command execution. For hostnames, restrict to DNS-safe characters; for numeric IDs, enforce integer parsing and range checks.

from flask import request, jsonify
import re

def validate_hostname(host):
    # Allow lowercase letters, digits, hyphens, and dots; no shell metacharacters
    pattern = re.compile(r'^[a-z0-9]([a-z0-9\-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]*[a-z0-9])?)*$')
    return bool(pattern.fullmatch(host))

@app.route('/check', methods=['GET'])
def check():
    host = request.args.get('host', '')
    if not validate_hostname(host):
        return jsonify({'error': 'Invalid host'}), 400
    # Proceed with safe command usage
    return jsonify({'host': host})

4. Principle of least privilege and environment hardening

Run the Flask application with minimal OS permissions. Ensure environment variables containing credentials are not exposed in logs or error pages. Disable debug mode in production to prevent code execution via introspection.

# Run the app with a non-root user and restricted environment
# Example launch command (not part of app code):
# export FLASK_ENV=production
# export API_USER=svcuser
# export API_PASS=$(openssl rand -base64 16)
# flask run --host=0.0.0.0 --port=5000

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

How can I test if my Flask Basic Auth endpoint is vulnerable to command injection?
Send crafted input containing shell metacharacters (e.g., host=;id) to the endpoint and inspect logs and responses. Do not rely on error messages alone; use automated scanning with tools that simulate injection attempts while monitoring behavior.
Does using Basic Auth alone protect against command injection?
No. Basic Authentication handles credential verification only. Command injection depends on how user-controlled data is handled after authentication. Always validate and sanitize inputs and avoid invoking the shell.