OS Command Injection: Examples and Prevention
OS command injection occurs when an application builds a shell command using user input. Shells interpret special characters like ;, &&, | and backticks, so an attacker can append their own commands.
Typical vulnerable code
Developers often reach for the shell to do something quickly: ping a host, convert a file, generate a thumbnail or call a CLI tool.
// Node.js, vulnerable
const { exec } = require("child_process");
exec(`ping -c 1 ${req.query.host}`, (err, out) => res.send(out));
With host=8.8.8.8; id, the server runs id as well.
// PHP, vulnerable
system("nslookup " . $_GET["domain"]);
Shell metacharacters attackers use
| Character | Effect |
|---|---|
; | Run another command |
&& and double pipe | Run conditionally |
| Single pipe | Pipe output into another command |
Backticks and $() | Command substitution |
| Newline | Start a new command |
Blind command injection
Often the output is not returned. Attackers then use:
- Time delays, such as
sleep 10, to confirm execution. - Out-of-band callbacks, such as a DNS lookup or HTTP request to a server they control.
- Writing files to a web-accessible location.
How to prevent command injection
1. Avoid the shell entirely. Use a library for the task, such as an image processing library instead of calling ImageMagick through a shell.
2. If you must run a program, pass arguments as an array without a shell.
// Node.js, safe
const { execFile } = require("child_process");
execFile("ping", ["-c", "1", host], (err, out) => res.send(out));
# Python, safe
subprocess.run(["ping", "-c", "1", host], check=True)
3. Validate input strictly. For a hostname, allow only valid hostname characters. For an IP, parse it as an IP.
4. Watch for argument injection. Even without a shell, an input starting with - can be interpreted as an option. Use -- to end options where supported.
5. Run with least privilege so a successful injection has limited impact.
Where to look in your codebase
Search for exec, system, popen, shell_exec, passthru, spawn with shell: true, subprocess with shell=True, and backticks in Ruby or PHP. Review each call for user-influenced input.
Testing for command injection
SecurityInfinity agents test parameters for command injection using safe, time-based and out-of-band techniques, confirm execution, and point to the vulnerable call. Start free.
Frequently asked questions
Is escaping user input enough?
Escaping functions exist, but they are easy to misuse. Avoiding the shell and passing arguments as arrays is much safer.
What is the difference between command injection and code injection?
Command injection runs operating system commands. Code injection runs code in the application's own language, for example via eval().
How severe is command injection?
Usually critical, since it typically gives the attacker the ability to run arbitrary commands with the application's privileges.