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=Trueonly when necessary. Explicitly escape POSIX shell arguments withshlex.quote()orshlex.join(). - Account for platform differences:
shlextargets Unix/POSIX shells; do not assume it also protects Windows shell commands. Withshell=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
- OWASP: Command Injection
- CWE: CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection')
- Unix shell quoting:
shlex.quote