Command Injection in Fastapi with Jwt Tokens
Command Injection in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Command Injection occurs when an attacker can inject and execute arbitrary system commands. In FastAPI applications that handle JWT tokens, the risk emerges when decoded token payloads are passed to shell commands without proper sanitization. FastAPI itself does not execute shell commands, but developer code that processes JWT claims (e.g., user-supplied fields or dynamic parameters derived from token data) can introduce command injection if input is concatenated into subprocess calls.
Consider a scenario where a FastAPI endpoint decodes a JWT and uses a claim such as username or tenant_id in a shell command. If the developer uses Python’s subprocess module and interpolates token-derived data directly into the command string, an attacker who controls the JWT (via a stolen token, injection in an otherwise trusted claim, or account creation flow) can escape intended data and execute arbitrary commands on the host. Common patterns include:
- Using
os.systemorsubprocess.run(..., shell=True)with string concatenation or interpolation that includes data from the JWT payload. - Building dynamic CLI commands for user management, file operations, or diagnostics where the JWT subject or role influences arguments without input validation or escaping.
Because JWT tokens are often trusted by developers (they are signed and may carry authorization information), unsafe handling of claims can bypass expected access controls. An attacker might present a token with a maliciously crafted claim that, when used in a shell context, changes command semantics (e.g., adding && id or backticks). Even when authentication is enforced via JWT verification middleware, authorization flaws may allow elevated claims to be abused in command construction if input validation is weak.
The vulnerability is not inherent to JWT or FastAPI, but arises from insecure coding practices where external data (including trusted-appearing token claims) reaches the OS command layer. For example, a FastAPI route that decodes a token and runs a system command like ping {username} without strict allow-listing or escaping is exposed. The presence of JWTs can make the attack surface less obvious because the token is often considered an authentication boundary, but the command injection risk remains tied to how the application uses decoded claims.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on avoiding shell interpretation of any data derived from JWT claims and enforcing strict input validation. The safest approach is to avoid shell=True and never interpolate token data into command strings. Use parameterized APIs and allow-listing where possible.
Example of vulnerable code to avoid:
import subprocess
from fastapi import Depends, FastAPI
import jwt
app = FastAPI()
def get_current_user(token: str):
# Simplified: use a proper JWT dependency in practice
return jwt.decode(token, options={"verify_signature": False})
@app.get("/ping-user")
def ping_user(user: dict = Depends(get_current_user)):
username = user.get("username", "")
# Dangerous: shell injection via JWT-derived data
result = subprocess.run(f"ping -c 1 {username}", shell=True, capture_output=True, text=True)
return {"output": result.stdout}
Secure remediation using subprocess.run with a list of arguments and no shell:
import subprocess
from fastapi import Depends, FastAPI, HTTPException
import jwt
app = FastAPI()
def get_current_user(token: str):
# In production, verify signature and validate claims properly
return jwt.decode(token, options={"verify_signature": False})
@app.get("/ping-user")
def ping_user(user: dict = Depends(get_current_user)):
username = user.get("username", "")
# Validate and allow-list known-safe characters for hostnames or identifiers
if not username.replace("-", "").replace(".", "").isalnum():
raise HTTPException(status_code=400, detail="Invalid username")
# Safe: arguments as list, shell=False (default)
result = subprocess.run(["ping", "-c", "1", username], capture_output=True, text=True, timeout=5)
return {"output": result.stdout}
Additional recommendations specific to JWT handling:
- Validate and restrict claims used in command construction. If a claim is not needed for OS operations, do not pass it.
- Use allow-listing for values that influence external commands (e.g., known usernames or tenant identifiers) rather than trying to escape arbitrary input.
- Apply principle of least privilege to the runtime identity of the FastAPI process to limit the impact of any potential injection.
- Audit any dynamic command building, and prefer native SDKs or APIs over invoking command-line utilities when possible.
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 |