# Command Injection via subprocess.shell=True

Language: Python
Severity: Critical
CWE: CWE-78

## Source
3

## Flow
3-4-5

## Sink
5

## Vulnerable Code
```python
import subprocess

def provision_iot_device(device_mac, firmware_url):
    log_path = f"/var/log/iot/provision_{device_mac}.log"
    cmd = f"wget {firmware_url} -O /tmp/firmware.bin && sha256sum /tmp/firmware.bin > {log_path}"
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    if result.returncode == 0:
        return {"status": "provisioned", "device": device_mac, "log": log_path}
    return {"status": "failed", "error": result.stderr}
```

## Explanation

The function takes user-controlled parameters (device_mac, firmware_url) and directly interpolates them into a shell command string without any sanitization or validation. This command is then executed via subprocess.run() with shell=True, allowing an attacker to inject arbitrary shell commands through either parameter.

## Remediation

The fix eliminates command injection by avoiding shell=True and instead using subprocess with argument lists, which prevents shell metacharacter interpretation. Input validation is added to ensure the MAC address matches a strict regex pattern and the firmware URL uses only http/https schemes. File writing is done via Python's built-in file I/O instead of shell redirection.

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

def provision_iot_device(device_mac, firmware_url):
    # Validate MAC address format
    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
    parsed_url = urllib.parse.urlparse(firmware_url)
    if parsed_url.scheme not in ('http', 'https'):
        return {"status": "failed", "error": "Invalid URL scheme. Only http and https are allowed."}
    if not parsed_url.hostname:
        return {"status": "failed", "error": "Invalid URL: no hostname found"}
    
    log_path = f"/var/log/iot/provision_{device_mac}.log"
    firmware_path = "/tmp/firmware.bin"
    
    # Download firmware using subprocess without shell=True
    wget_result = subprocess.run(
        ["wget", firmware_url, "-O", firmware_path],
        capture_output=True, text=True
    )
    if wget_result.returncode != 0:
        return {"status": "failed", "error": wget_result.stderr}
    
    # Generate checksum using subprocess without shell=True
    sha_result = subprocess.run(
        ["sha256sum", firmware_path],
        capture_output=True, text=True
    )
    if sha_result.returncode != 0:
        return {"status": "failed", "error": sha_result.stderr}
    
    # Write checksum to log file using Python file I/O
    with open(log_path, 'w') as log_file:
        log_file.write(sha_result.stdout)
    
    return {"status": "provisioned", "device": device_mac, "log": log_path}
```
