OS Command Injection
1. Definition
OS Command Injection (or Shell Injection) is a vulnerability that allows an attacker to execute arbitrary operating system commands on the server that is running an application. This typically happens when the application passes unsafe user-supplied data (forms, cookies, headers) to a system shell.
It differs from Code Injection (where application code like PHP/Node is executed). Here, the attacker exploits the shell (bash, sh, cmd.exe).
2. Technical Explanation
Many applications need to call underlying OS programs (e.g., using ping to check network status or ffmpeg to process video). If the application uses a function that invokes a shell (like C’s system(), PHP’s shell_exec(), or Node’s exec()) and concatenates arguments as a string:
Note: Java’s
Runtime.exec()is a common point of confusion; it does not invoke a shell by default, meaning shell metacharacters are not interpreted unless the developer explicitly calls/bin/sh -c.
// Vulnerable Node.js
const { exec } = require('child_process');
exec('ping -c 1 ' + userInput, (err, stdout) => { ... });If the user input is google.com, it runs ping -c 1 google.com. But if the input is google.com; rm -rf /, the shell sees the semicolon ; as a command separator and executes:
ping -c 1 google.comrm -rf /(Deletes files)
3. Attack Flow
sequenceDiagram
participant Attacker
participant App as Web Server
participant OS as Operating System
Attacker->>App: GET /dns-lookup?hostname=google.com; cat /etc/passwd
Note over App: App concatenates string:<br/>"nslookup google.com; cat /etc/passwd"
App->>OS: Execute Command via Shell
OS->>OS: 1. Run nslookup (Success)
OS->>OS: 2. Run cat /etc/passwd (Success)
OS-->>App: Returns Combined Output
App-->>Attacker: Displays DNS result + System User List4. Real-World Case Study: Shellshock (2014)
Target: Global Linux/Unix Systems (Bash vulnerability). Vulnerability Class: OS Command Injection (Environment Variables). CVE: CVE-2014-6271.
The Mechanics: Bash had a flaw where it unintentionally executed commands stored in environment variables if they were defined after a function definition () { :;};. Web servers (like Apache via CGI) map HTTP headers (like User-Agent) to environment variables.
The Attack: Attackers sent HTTP requests with a crafted User-Agent: User-Agent: () { :; }; /bin/cat /etc/passwd
When the server processed the request, it set the environment variable and triggered the bug, executing cat /etc/passwd immediately.
Impact: millions of servers were compromised. It allowed full remote code execution (RCE) on any exposed system using Bash-based CGI scripts, DHCP clients, or SSH restricted shells.
5. Detailed Defense Strategies
A. Avoid Shell Execution
The best defense is to use native language APIs instead of shelling out.
- Bad:
exec("mkdir " + dirName) - Good:
fs.mkdir(dirName)(Node.js) oros.mkdir(dirName)(Python).
B. Use execFile or Argument Arrays
If you must run an external command, use functions that accept arguments as an Array rather than a string. This prevents the shell from interpreting separators (&, ;, |).
Node.js Example:
// Safe: Shell is NOT spawned const { execFile } = require('child_process'); execFile('ping', ['-c', '1', userInput], (error, stdout) => { ... });If
userInputisgoogle.com; ls, the OS tries to ping a host literally named"google.com; ls", which fails harmlessly.
C. Strong Input Validation (Allowlist)
If you must use exec():
- Validate the input against a strict Regex (e.g.,
^[a-zA-Z0-9.-]+$). - Reject any input containing shell metacharacters:
&,;,|,$,>,<,`,!.
D. Run with Least Privilege
Ensure the web application runs as a user with minimal permissions (e.g., www-data). It should not have root access, and it should not be able to write to sensitive directories.
