Command Injection in Fastapi with Basic Auth
Command Injection in Fastapi with Basic Auth — how this specific combination creates or exposes the vulnerability
Command Injection occurs when untrusted input is concatenated into a system command, allowing an attacker to execute arbitrary shell commands. In Fastapi, this risk can emerge even when the endpoint is protected by HTTP Basic Authentication. Basic Auth provides a transport-level identity check but does not sanitize or validate command arguments. If user-controlled data (e.g., a username, header, or query parameter) is passed to a subprocess call without proper validation or escaping, the boundary between identity and authorization blurs: authentication confirms who is making the request, but does nothing to prevent that authenticated user from supplying malicious input that reaches the shell.
A typical pattern is an authenticated endpoint that invokes a system utility using Python’s subprocess module. For example, an endpoint might accept a filename via a query parameter and pass it to tar or grep. If the parameter is used in a shell command string without escaping, an authenticated attacker can inject additional shell commands using shell metacharacters such as &&, ;, or backticks. Because the endpoint requires a valid username and password, the attacker must first obtain credentials (or use a leaked set), but once authenticated, the unsafe command construction enables privilege escalation, data exfiltration, or further network pivoting within the runtime environment.
In a black-box scan, middleBrick’s checks for Authentication and Unsafe Consumption run in parallel. The scanner first validates that Basic Auth is required and that credentials are necessary to reach the endpoint. It then probes the endpoint with authenticated requests, attempting to inject command sequences via input vectors that reach subprocess calls or OS utilities. If the API reflects injected output or changes system state without validation, the scan surfaces an Unsafe Consumption finding mapped to the Authentication context, highlighting that identity enforcement does not equate to input safety.
Basic Auth-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on removing shell interpretation of untrusted data and enforcing strict input validation. Do not rely on Basic Auth alone to secure command execution. Instead, avoid shell=True, use parameterized APIs, and validate input against an allowlist.
Example: Unsafe endpoint with Basic Auth (vulnerable)
from fastapi import FastAPI, Depends, HTTPException, Header
import subprocess
import shlex
app = FastAPI()
def get_current_user(authorization: str = Header(...)):
# Simplified Basic Auth check for example
if not authorization.startswith("Basic "):
raise HTTPException(status_code=401, detail="Unauthorized")
# In real code, decode and validate credentials
return {"username": "alice"}
@app.get("/archive")
def list_files(name: str, user: dict = Depends(get_current_user)):
# UNSAFE: shell=True with unsanitized input
result = subprocess.run(f"tar -tf {name}", shell=True, capture_output=True, text=True)
return {"output": result.stdout}
In the above, an authenticated user can supply name=file.tar;cat%20/etc/passwd and execute arbitrary commands. Basic Auth is present but does not mitigate command injection.
Example: Secured endpoint with validation and no shell
from fastapi import FastAPI, Depends, HTTPException, Header
import subprocess
app = FastAPI()
def get_current_user(authorization: str = Header(...)):
if not authorization.startswith("Basic "):
raise HTTPException(status_code=401, detail="Unauthorized")
return {"username": "alice"}
ALLOWED_NAMES = {"backup", "report", "logs"}
@app.get("/archive")
def list_files(name: str, user: dict = Depends(get_current_user)):
# Validate input against an allowlist
if name not in ALLOWED_NAMES:
raise HTTPException(status_code=400, detail="Invalid archive name")
# Use a list, shell=False (default) to avoid shell interpretation
result = subprocess.run(["tar", "-tf", name], capture_output=True, text=True, timeout=10)
return {"output": result.stdout}
If you must construct command-like arguments dynamically, use shlex.quote on each argument and keep shell=False, but prefer an allowlist as shown. Basic Auth remains useful for identity, but input validation and safe subprocess usage are required to prevent command injection.
Example: Using shlex.quote when dynamic arguments are unavoidable
import shlex
cmd = ["tar", "-tf", shlex.quote(name)]
subprocess.run(cmd, capture_output=True)
These patterns align with secure coding practices and map to findings in the BFLA/Privilege Escalation and Input Validation checks, which run alongside Authentication in middleBrick scans.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |