{"title":"Command Injection via subprocess.run(shell=True)","language":"Python","severity":"Critical","cwe":"CWE-78","source_lines":[1],"flow_lines":[1,3,4,5,1,3,8,9,7],"sink_lines":[5,7],"vulnerable_code":"import subprocess\n\ndef provision_iot_device(device_mac, firmware_version):\n    device_id = device_mac.replace(':', '').lower()\n    provision_cmd = f\"aws iot create-thing --thing-name device_{device_id} --attribute-payload attributes={{firmware={firmware_version}}}\"\n    result = subprocess.run(provision_cmd, shell=True, capture_output=True, text=True)\n    if result.returncode == 0:\n        cert_cmd = f\"openssl req -new -x509 -days 365 -key /keys/{device_id}.key -out /certs/{device_id}.crt -subj '/CN={device_mac}'\"\n        subprocess.run(cert_cmd, shell=True, check=True)\n        return {\"status\": \"provisioned\", \"device_id\": device_id}\n    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":"import subprocess\nimport re\n\ndef validate_mac_address(mac):\n    \"\"\"Validate MAC address format strictly.\"\"\"\n    pattern = r'^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$'\n    if not re.match(pattern, mac):\n        raise ValueError(f\"Invalid MAC address format: {mac}\")\n    return mac\n\ndef validate_firmware_version(version):\n    \"\"\"Validate firmware version format strictly (e.g., v1.0, v2.3.1).\"\"\"\n    pattern = r'^v?\\d+(\\.\\d+){0,3}$'\n    if not re.match(pattern, version):\n        raise ValueError(f\"Invalid firmware version format: {version}\")\n    return version\n\ndef provision_iot_device(device_mac, firmware_version):\n    # Validate inputs before use\n    device_mac = validate_mac_address(device_mac)\n    firmware_version = validate_firmware_version(firmware_version)\n    \n    device_id = device_mac.replace(':', '').lower()\n    \n    # Use subprocess with argument list (shell=False) to prevent command injection\n    provision_cmd = [\n        \"aws\", \"iot\", \"create-thing\",\n        \"--thing-name\", f\"device_{device_id}\",\n        \"--attribute-payload\", f\"attributes={{firmware={firmware_version}}}\"\n    ]\n    result = subprocess.run(provision_cmd, capture_output=True, text=True)\n    \n    if result.returncode == 0:\n        cert_cmd = [\n            \"openssl\", \"req\", \"-new\", \"-x509\",\n            \"-days\", \"365\",\n            \"-key\", f\"/keys/{device_id}.key\",\n            \"-out\", f\"/certs/{device_id}.crt\",\n            \"-subj\", f\"/CN={device_mac}\"\n        ]\n        subprocess.run(cert_cmd, check=True)\n        return {\"status\": \"provisioned\", \"device_id\": device_id}\n    return {\"status\": \"failed\", \"error\": result.stderr}"}