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

Language: Python
Severity: Critical
CWE: CWE-78

## Source
1

## Flow
1-3-4-5, 1-3-8-9-7

## Sink
5, 7

## Vulnerable Code
```python
import subprocess

def provision_iot_device(device_mac, firmware_version):
    device_id = device_mac.replace(':', '').lower()
    provision_cmd = f"aws iot create-thing --thing-name device_{device_id} --attribute-payload attributes={{firmware={firmware_version}}}"
    result = subprocess.run(provision_cmd, shell=True, capture_output=True, text=True)
    if result.returncode == 0:
        cert_cmd = f"openssl req -new -x509 -days 365 -key /keys/{device_id}.key -out /certs/{device_id}.crt -subj '/CN={device_mac}'"
        subprocess.run(cert_cmd, shell=True, check=True)
        return {"status": "provisioned", "device_id": device_id}
    return {"status": "failed", "error": result.stderr}
```

## Explanation

The function accepts untrusted user input (device_mac and firmware_version) and directly embeds them into shell commands executed via subprocess.run(shell=True). An attacker can inject arbitrary shell commands through these parameters, leading to command injection vulnerabilities at both the AWS IoT provisioning step and the OpenSSL certificate generation step.

## Remediation

The fix eliminates command injection by replacing shell=True subprocess calls with argument lists (shell=False), which prevents shell metacharacter interpretation. Additionally, strict input validation using regular expressions ensures that device_mac and firmware_version conform to expected formats before being used in any commands, providing defense-in-depth against injection attacks.

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

def validate_mac_address(mac):
    """Validate MAC address format strictly."""
    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_version(version):
    """Validate firmware version format strictly (e.g., v1.0, v2.3.1)."""
    pattern = r'^v?\d+(\.\d+){0,3}$'
    if not re.match(pattern, version):
        raise ValueError(f"Invalid firmware version format: {version}")
    return version

def provision_iot_device(device_mac, firmware_version):
    # Validate inputs before use
    device_mac = validate_mac_address(device_mac)
    firmware_version = validate_firmware_version(firmware_version)
    
    device_id = device_mac.replace(':', '').lower()
    
    # Use subprocess with argument list (shell=False) to prevent command injection
    provision_cmd = [
        "aws", "iot", "create-thing",
        "--thing-name", f"device_{device_id}",
        "--attribute-payload", f"attributes={{firmware={firmware_version}}}"
    ]
    result = subprocess.run(provision_cmd, capture_output=True, text=True)
    
    if result.returncode == 0:
        cert_cmd = [
            "openssl", "req", "-new", "-x509",
            "-days", "365",
            "-key", f"/keys/{device_id}.key",
            "-out", f"/certs/{device_id}.crt",
            "-subj", f"/CN={device_mac}"
        ]
        subprocess.run(cert_cmd, check=True)
        return {"status": "provisioned", "device_id": device_id}
    return {"status": "failed", "error": result.stderr}
```
