flawopen.com/Command Injection/Python
Imagine asking an assistant to print a document named 'report.pdf'. Command injection happens when someone gives the filename 'report.pdf; rm -rf /', and the assistant blindly hands that whole sentence to the computer terminal, causing it to print the report and then wipe the hard drive.
In Python, command injection almost always stems from invoking os.system() or subprocess.run(..., shell=True) with string concatenation or f-strings. When shell=True is set, Python spawns an intermediate system shell (/bin/sh -c) to parse the string, enabling attackers to inject additional shell instructions.
In 2021, a command injection vulnerability in Log4j (and countless network appliance admin portals including Palo Alto PAN-OS and Fortinet) allowed unauthenticated remote attackers to execute arbitrary root shell commands, leading to widespread ransomware deployments.
CISA Cybersecurity Advisory & MITRE CVE repository.# shell=True spawns /bin/sh to interpret the string
import subprocess
def ping_host(host):
# Attacker input: "8.8.8.8; cat /etc/passwd"
cmd = f"ping -c 1 {host}"
return subprocess.run(cmd, shell=True, capture_output=True)
# shell=False (default): passes arguments directly to the binary
import subprocess
def ping_host(host):
# host is treated strictly as a single argument to ping
cmd = ["ping", "-c", "1", host]
return subprocess.run(cmd, shell=False, capture_output=True, check=True)
When passing arguments as a list with shell=False, Python calls the OS kernel's execve directly. The host argument is passed as a discrete memory buffer to the ping binary; no shell parser is invoked, meaning semicolons, pipes, and backticks have zero syntactic meaning.
os.system() has no array syntax. It always passes its string argument to /bin/sh -c. It should never be used with user-controlled input.
shlex.quote() is POSIX-specific and fails on Windows (where cmd.exe uses different escape semantics). Avoiding shell execution entirely is vastly superior to escaping.
Regex character blacklists almost always miss shell metacharacters like newlines ( ), IFS variable substitutions, or Unicode separators. Direct execution eliminates the need to guess metacharacters.
grep -rn "subprocess.*shell=True" --include="*.py" .
grep -rn "os\.system(" --include="*.py" .
["executable", "arg1", "arg2"]shell=False (the default in subprocess)os.system() and os.popen()Only when you specifically require built-in shell features like environment variable expansion ($HOME) or pipe chaining. In those cases, use Python's built-in libraries (os.environ, subprocess.PIPE) instead of the shell.