HIGH command injectionfastapihmac signatures

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.run with 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_digest to 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 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

Does computing an HMAC over user input guarantee protection against command injection in FastAPI?
No. HMAC ensures data integrity but does not prevent injection if the server builds commands by concatenating the validated input into a shell command. Always use structured subprocess APIs and avoid string-based command assembly.
What is a safer alternative to using HMAC plus shell commands in FastAPI?
Map the HMAC-verified identifier to a predefined, server-side canonical value and use subprocess with argument lists (e.g., subprocess.run(["ffmpeg", "-i", safe_path])) instead of building shell command strings.