Command Injection in Fastapi with Hmac Signatures
Command Injection in Fastapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Command injection occurs when an attacker can control part of a system command executed by the application. In FastAPI, combining dynamic command construction with HMAC-based integrity checks can still lead to injection if the HMAC is applied only to portions of the data or if the runtime value used in the command is not validated before the signature check.
Consider a scenario where a FastAPI endpoint receives a filename parameter, computes an HMAC over that parameter using a shared secret, and then uses the parameter in a shell command (for example, to call an external utility). If the server-side code verifies the HMAC but then directly interpolates the user-supplied value into the command string, the attacker who can guess or leak a valid HMAC for a benign input might attempt to inject shell metacharacters. Even when the HMAC is computed over the raw input, if the server reconstructs the command by concatenating strings instead of using a safe interface, the attacker can break out of the intended argument boundaries and execute arbitrary commands.
Example of a vulnerable pattern: the developer hashes the filename with HMAC to ensure it has not been tampered with, but then builds the command using Python string formatting. An attacker who obtains a valid HMAC for a simple filename like report.txt might submit report.txt; cat /etc/passwd. If the server recomputes the HMAC over the full string and the verification passes (because the attacker somehow knows the secret or the MAC is leaked), the command executed becomes ffmpeg -i report.txt; cat /etc/passwd, leading to command injection.
Root causes specific to this combination include: (1) constructing shell commands via string concatenation with external input even after HMAC verification, (2) allowing the HMAC to cover only a subset of the data while trusting other parts implicitly, and (3) failing to treat HMAC validation as a prerequisite for safe parsing rather than as a replacement for input validation and command construction safety.
Additionally, if the FastAPI service runs in an environment where the HMAC verification logic and the command builder are in different modules or microservices, discrepancies in how the signature is computed (e.g., different normalization or encoding) can create a window where a value appears valid but is later misinterpreted by the shell, enabling injection.
Hmac Signatures-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on ensuring that HMAC verification is applied to the exact data that will be used in command construction, and that command execution never directly interpolates user input. Use structured APIs for subprocess invocation and validate input against a strict allowlist when possible.
Correct HMAC usage in FastAPI:
import hmac
import hashlib
from fastapi import FastAPI, HTTPException, Query
import subprocess
app = FastAPI()
SECRET = b"super-secret-key" # store securely, e.g. via environment variable
def verify_hmac(value: str, received_mac: str) -> bool:
computed = hmac.new(SECRET, value.encode("utf-8"), hashlib.sha256).hexdigest()
return hmac.compare_digest(computed, received_mac)
@app.get("/process")
async def process_file(file_id: str = Query(..., description="File identifier"), signature: str = Query(..., description="HMAC-SHA256 of file_id")):
if not verify_hmac(file_id, signature):
raise HTTPException(status_code=400, detail="Invalid signature")
# Safe: use a controlled mapping to a real path; do not directly use file_id in shell commands
allowed_files = {
"abc123": "report_2024.csv",
"def456": "summary_2024.csv",
}
if file_id not in allowed_files:
raise HTTPException(status_code=400, detail="File not allowed")
safe_filename = allowed_files[file_id]
# Use subprocess with a list to avoid shell injection
result = subprocess.run(["ffmpeg", "-i", safe_filename], capture_output=True, text=True)
return {"stdout": result.stdout, "stderr": result.stderr}
Key practices illustrated:
- Compute HMAC over the exact identifier that the server will use to look up a safe, canonical value (e.g., a mapping key).
- Never build shell commands by concatenating user input; use
subprocess.runwith a list of arguments to avoid invoking a shell. - Maintain a strict allowlist (or a secure mapping) so that even after HMAC verification, the runtime value used in system interactions is controlled and predictable.
- Use
hmac.compare_digestto prevent timing attacks on the signature comparison.
If you must pass dynamic arguments to an external binary, validate and sanitize each component, and prefer using the structured invocation patterns shown above rather than constructing a shell command string.
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 |