What Is Command Injection?
Command injection occurs when an application passes untrusted input to a system shell or OS command execution function — such as exec(), system(), subprocess.run(shell=True), or Node's child_process.exec() — without properly separating the command from its arguments. Because shells interpret special characters like ;, &&, |, and backticks as command separators or substitution operators, an attacker can append entirely new commands to whatever the application intended to run, executing them with the same privileges as the vulnerable application process.
Cricket analogy: It is like a groundskeeper who takes a written request 'water the pitch' and reads it aloud to a machine that also understands punctuation as new instructions, so a request ending in '; also unlock the equipment shed' gets carried out too.
Exploitation Techniques
A common real-world example is a network diagnostic tool on a router's web interface that lets a user 'ping' a hostname; if the backend runs system("ping -c 4 " + userInput), an attacker submitting 8.8.8.8; cat /etc/passwd causes the shell to run the ping command and then separately dump the password file. Attackers also use command substitution syntax like backticks or $() to embed a command's output directly into another command's arguments, and blind command injection (where no output is returned) can be exploited using out-of-band techniques such as triggering a DNS lookup or an outbound curl request to an attacker-controlled server to confirm execution.
Cricket analogy: It is like submitting a request to 'check the pitch moisture; also, hand me the ground curator's master key' — the system executes both parts because it never validated that only a moisture reading was requested.
# VULNERABLE: user input passed directly to a shell
import subprocess
def ping_host(hostname):
# attacker sends: 8.8.8.8; cat /etc/passwd
result = subprocess.run(f"ping -c 4 {hostname}", shell=True, capture_output=True)
return result.stdout
# SAFE: arguments passed as a list, no shell interpretation of separators
import subprocess
import shlex
def ping_host_safe(hostname):
# still validate hostname format before use, e.g. against a strict regex
result = subprocess.run(["ping", "-c", "4", hostname], shell=False, capture_output=True)
return result.stdoutCommand Injection vs SQL Injection
Both vulnerabilities share the same root cause — untrusted input mixed with a command interpreter's syntax — but command injection is often more severe because it grants direct access to the underlying operating system rather than just the database. A successful command injection can read arbitrary files, install malware, establish a reverse shell for persistent remote access, or pivot to other systems on the network, whereas SQL Injection is typically constrained to whatever the database engine and its configured permissions allow.
Cricket analogy: SQLi is like breaching the scorer's table and altering the scorebook, while command injection is like walking straight into the groundskeeper's shed and taking control of the entire stadium's infrastructure.
Detecting and Mitigating
The most reliable fix is to avoid shell invocation entirely: use library APIs (e.g. a DNS resolution library instead of shelling out to ping) or, when a subprocess is genuinely necessary, call the executable with an argument array (shell=False / execve-style invocation) rather than a single interpolated string, so shell metacharacters are never interpreted. When shell use is unavoidable, strictly allow-list expected input (e.g. validate a hostname against a regex that only permits alphanumeric characters, dots, and hyphens) rather than trying to blocklist dangerous characters, since blocklists are notoriously easy to bypass with encoding tricks or unexpected shell features.
Cricket analogy: It is like replacing a verbal request to the curator with a fixed dropdown menu of pre-approved pitch treatments, so no one can ever type in an arbitrary instruction disguised as a request.
Never try to sanitize dangerous input by stripping or escaping specific characters like ; or | as your only defense — attackers routinely bypass blocklists using alternate encodings, newline characters, or shell features you didn't anticipate. Prefer argument-array execution or allow-listing instead.
- Command injection happens when untrusted input reaches a shell or OS command execution function.
- Shell metacharacters like
;,&&,|, and backticks let attackers append or substitute commands. - Blind command injection can be confirmed using out-of-band DNS or HTTP callbacks.
- Command injection often grants OS-level access, making it typically more severe than SQL Injection.
- The best fix is avoiding shell invocation entirely or using argument-array execution (shell=False).
- Allow-listing valid input is far more reliable than blocklisting dangerous characters.
- Successful exploitation can lead to reverse shells, malware installation, and lateral network movement.
Practice what you learned
1. What is the root cause of command injection vulnerabilities?
2. Which technique confirms a blind command injection when no output is returned to the attacker?
3. Why is command injection often considered more severe than SQL Injection?
4. What is the most reliable way to prevent command injection when a subprocess call is unavoidable?
5. Why is blocklisting dangerous characters considered a weak defense against command injection?
Was this page helpful?
You May Also Like
SQL Injection Explained
Learn how attackers manipulate SQL queries by injecting malicious input, and why this remains one of the most dangerous web application vulnerabilities.
Preventing SQL Injection
A practical guide to the defenses that actually stop SQL Injection: parameterized queries, safe ORM usage, input validation, least privilege, and WAFs.
Cross-Site Scripting (XSS) Explained
Understand how attackers inject malicious scripts into web pages viewed by other users, and the three major categories of XSS: stored, reflected, and DOM-based.