Some inputs make the server itself attack — fetching internal URLs (SSRF) or running shell commands. Both come from trusting input; both are stopped by strict allowlists.
Why: SSRF happens when an app fetches a URL supplied by the user without restriction, so an attacker points it at internal systems — cloud metadata endpoints, internal admin panels — that the server can reach but they cannot. When: any "fetch this URL / import from URL / webhook" feature is a candidate. Where: cloud metadata services (169.254.169.254) are a prime SSRF target for stealing credentials.
# VULNERABLE — fetches whatever URL the user provides:
@app.post("/api/fetch-image")
def fetch_image(url):
return requests.get(url).content # attacker sends internal URLs
# Attacker submits: http://169.254.169.254/latest/meta-data/iam/...
# -> the SERVER fetches cloud credentials and returns them. That's SSRF.Why: command injection is SQL injection’s shell cousin — user input concatenated into an OS command lets an attacker run arbitrary commands on the server, the most severe outcome possible. When: any use of a shell with untrusted input is dangerous. Where: the fix is the same principle as SQLi — never build the command from strings; pass arguments as a list to the program directly, no shell.
# VULNERABLE — user input in a shell command:
import os
os.system(f"ping -c 1 {host}") # host = "8.8.8.8; rm -rf /" -> runs both!
# SAFE — no shell, arguments passed as a list, input validated:
import subprocess, ipaddress
ipaddress.ip_address(host) # validate it's really an IP (raises if not)
subprocess.run(["ping", "-c", "1", host], shell=False, timeout=5)Why: both SSRF and command injection come from trusting input, so both are cured the same way — validate against a strict allowlist of what is permitted, never a denylist of what is forbidden. When: allow only the exact hosts/schemes or commands you intend; block everything else by default. Where: add network egress controls so even a successful SSRF cannot reach internal ranges or metadata endpoints.
ALLOWLIST, don't denylist:
SSRF -> allow only specific hosts + https; block internal IP ranges
(10/8, 172.16/12, 192.168/16, 127/8, 169.254/16) at the app AND
the network egress layer
CMD -> avoid shells entirely; if unavoidable, allow a fixed set of
commands with validated, list-passed arguments
Denylists always miss a bypass. Default-deny, allow the known-good.