# Command Injection via subprocess.run(shell=True)

Language: Python
Severity: Critical
CWE: CWE-78

## Source
3

## Flow
3-5, 3-6-7

## Sink
5, 7

## Vulnerable Code
```python
import subprocess

def provision_iot_device(device_mac, firmware_url):
    validation_cmd = f"ping -c 2 {device_mac.replace(':', '-')}.local && curl -o /tmp/fw.bin {firmware_url}"
    result = subprocess.run(validation_cmd, shell=True, capture_output=True, text=True)
    if result.returncode == 0:
        flash_cmd = f"iot-flasher --device {device_mac} --firmware /tmp/fw.bin"
        subprocess.run(flash_cmd, shell=True)
        return {"status": "provisioned", "device": device_mac}
    return {"status": "failed", "error": result.stderr}
```

## Explanation

The function accepts untrusted user input (device_mac and firmware_url) and directly interpolates them into shell commands executed via subprocess.run(shell=True). An attacker can inject arbitrary shell commands through these parameters, leading to complete system compromise.

## Remediation

The fix eliminates command injection by replacing all subprocess.run(shell=True) calls with argument-list form (shell=False), which prevents shell interpretation of special characters. Additionally, strict input validation is applied to both the MAC address (regex whitelist) and firmware URL (scheme validation and dangerous character rejection) before they are used in any subprocess call.

## Secure Code
```python
import subprocess
import re
from urllib.parse import urlparse


def validate_mac_address(mac):
    """Validate that the input is a properly formatted MAC address."""
    pattern = r'^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$'
    if not re.match(pattern, mac):
        raise ValueError(f"Invalid MAC address format: {mac}")
    return mac


def validate_firmware_url(url):
    """Validate that the firmware URL is a proper HTTP/HTTPS URL with safe characters."""
    parsed = urlparse(url)
    if parsed.scheme not in ('http', 'https'):
        raise ValueError(f"Invalid URL scheme: {parsed.scheme}. Only http and https are allowed.")
    if not parsed.hostname:
        raise ValueError("URL must have a valid hostname.")
    if any(c in url for c in [';', '|', '&', '`', '$', '(', ')', '{', '}', '\n', '\r']):
        raise ValueError("URL contains invalid characters.")
    return url


def provision_iot_device(device_mac, firmware_url):
    device_mac = validate_mac_address(device_mac)
    firmware_url = validate_firmware_url(firmware_url)

    device_hostname = device_mac.replace(':', '-') + '.local'

    ping_result = subprocess.run(
        ['ping', '-c', '2', device_hostname],
        capture_output=True, text=True
    )
    if ping_result.returncode != 0:
        return {"status": "failed", "error": "Device not reachable: " + ping_result.stderr}

    curl_result = subprocess.run(
        ['curl', '-o', '/tmp/fw.bin', '--', firmware_url],
        capture_output=True, text=True
    )
    if curl_result.returncode != 0:
        return {"status": "failed", "error": "Firmware download failed: " + curl_result.stderr}

    flash_result = subprocess.run(
        ['iot-flasher', '--device', device_mac, '--firmware', '/tmp/fw.bin'],
        capture_output=True, text=True
    )
    if flash_result.returncode != 0:
        return {"status": "failed", "error": "Flashing failed: " + flash_result.stderr}

    return {"status": "provisioned", "device": device_mac}
```
