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

Language: Python
Severity: Critical
CWE: CWE-78

## Source
3, 4

## Flow
3-5-6, 4-8-9

## Sink
6, 9

## Vulnerable Code
```python
import subprocess

def provision_iot_device(device_mac, firmware_url):
    device_id = device_mac.replace(':', '').lower()
    staging_path = f"/opt/iot/staging/{device_id}"
    download_cmd = f"curl -o {staging_path}/firmware.bin {firmware_url} && sha256sum {staging_path}/firmware.bin"
    result = subprocess.run(download_cmd, shell=True, capture_output=True, text=True)
    if result.returncode == 0:
        flash_cmd = f"iot-flasher --device {device_mac} --binary {staging_path}/firmware.bin --verify"
        subprocess.run(flash_cmd, shell=True)
        return {"status": "provisioned", "checksum": result.stdout.strip()}
    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() with shell=True. An attacker can inject malicious shell commands through these parameters, leading to arbitrary command execution on the IoT provisioning server.

## Remediation

The fix eliminates command injection by replacing shell=True with argument lists (shell=False), which prevents shell metacharacter interpretation. Additionally, strict input validation is applied to both the MAC address (requiring exact format match) and the firmware URL (allowing only safe characters with http/https scheme), providing defense-in-depth against malicious input.

## Secure Code
```python
import subprocess
import re
import os

def provision_iot_device(device_mac, firmware_url):
    # Validate MAC address format strictly
    if not re.match(r'^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$', device_mac):
        return {"status": "failed", "error": "Invalid MAC address format"}
    
    # Validate firmware URL (allow only http/https with safe characters)
    if not re.match(r'^https?://[a-zA-Z0-9.\-_/]+$', firmware_url):
        return {"status": "failed", "error": "Invalid firmware URL format"}
    
    device_id = device_mac.replace(':', '').lower()
    staging_path = f"/opt/iot/staging/{device_id}"
    
    # Ensure staging directory exists
    os.makedirs(staging_path, exist_ok=True)
    
    firmware_path = os.path.join(staging_path, "firmware.bin")
    
    # Download firmware using subprocess without shell=True
    download_result = subprocess.run(
        ["curl", "-o", firmware_path, "--", firmware_url],
        capture_output=True, text=True
    )
    if download_result.returncode != 0:
        return {"status": "failed", "error": download_result.stderr}
    
    # Compute checksum without shell=True
    checksum_result = subprocess.run(
        ["sha256sum", firmware_path],
        capture_output=True, text=True
    )
    if checksum_result.returncode != 0:
        return {"status": "failed", "error": checksum_result.stderr}
    
    # Flash the device without shell=True
    flash_result = subprocess.run(
        ["iot-flasher", "--device", device_mac, "--binary", firmware_path, "--verify"],
        capture_output=True, text=True
    )
    if flash_result.returncode == 0:
        return {"status": "provisioned", "checksum": checksum_result.stdout.strip()}
    
    return {"status": "failed", "error": flash_result.stderr}
```
