Command injection

Command injection

Description

Command injection occurs when an application executes user input as operating-system commands. An attacker can supply crafted input to run commands with the application's process privileges, potentially exposing data or damaging the system.

Potential impact

  • Misuse of process privileges: Files and services accessible to the process may be abused. Command execution does not automatically grant administrator privileges.
  • Data exposure: Sensitive data may be disclosed to an attacker.
  • Denial of service: Resource exhaustion or termination of important processes may interrupt service.

Remediation

  • Validate input: Strictly validate all user-controlled values used in commands.
  • Separate commands and arguments: Prefer argument lists with no shell, such as subprocess.run([...], shell=False).
  • Minimize shell use: Use shell=True only when necessary. Explicitly escape POSIX shell arguments with shlex.quote() or shlex.join().
  • Account for platform differences: shlex targets Unix/POSIX shells; do not assume it also protects Windows shell commands. With shell=True, placing user input in later list or tuple elements is not a general safety guarantee because platform behavior differs.

Examples

OS system

Before

python
# Unsafe os.system code
import os
from flask import request

@app.route('/command')
def unsafe_command():
    command = request.args.get('command')
    os.system(command)
    return "Command executed"

After

python
# Safe subprocess code
import subprocess
from flask import request, abort

@app.route('/command')
def safe_command():
    command = request.args.get('command')
    if command not in ['ls', 'pwd']:  # Run only allowed commands
        return abort(400, description="Invalid command")
    result = subprocess.run([command], shell=False, capture_output=True, text=True)
    return result.stdout

Explanation:

  • Before: Executes user input as an operating-system command without validation, allowing arbitrary commands.
  • After: Allows only predefined commands and passes an argument list with shell=False, avoiding shell metacharacter interpretation.

Paramiko exec_command

These excerpts omit Flask application and SSH credential configuration. Verify the SSH server's host key and confirm that its remote shell is POSIX-compatible. shlex.quote() escapes shell syntax but does not authorize a destination; validate host against the permitted destinations too.

Before

python
# Unsafe paramiko code
from flask import request
from paramiko.client import SSHClient

@app.route('/paramiko')
def paramiko():
    client = SSHClient()
    client.load_system_host_keys()
    client.connect("example.com", username=USER, password=PASS)
    client.exec_command(request.args.get("cmd")) # Noncompliant
    return "Command sent"

After

python
# Safe paramiko code
import subprocess
import shlex
from flask import request, abort
from paramiko.client import SSHClient

@app.route('/paramiko')
def paramiko():
    client = SSHClient()
    client.load_system_host_keys()
    client.connect("xeize.com", username=USER, password=PASS)
    
    health_cmd= "/bin/ping -c 3 -- %s"
    cmd = health_cmd % shlex.quote(request.args.get('host'))
    client.exec_command(cmd)
    return "Command sent"

Explanation:

  • Before: Passes unvalidated user input to a remote command, allowing arbitrary command execution.
  • After: Uses shlex.quote() to pass an argument as one token to a POSIX shell. Where possible, restrict execution to explicitly allowed commands as well.
  • shlex.quote() is designed for Unix shells and does not guarantee safety with non-POSIX shells such as Windows shells. Characters relevant to shell quoting include:
    • &
    • |
    • ;
    • $
    • >
    • <
    • `
    • \
    • !

References