{"title":"Command Injection via subprocess.run(shell=True)","language":"Python","severity":"Critical","cwe":"CWE-78","source_lines":[3,4],"flow_lines":[3,5,6,4,8,9],"sink_lines":[6,9],"vulnerable_code":"import subprocess\n\ndef provision_iot_device(device_mac, firmware_url):\n    device_id = device_mac.replace(':', '').lower()\n    staging_path = f\"/opt/iot/staging/{device_id}\"\n    download_cmd = f\"curl -o {staging_path}/firmware.bin {firmware_url} && sha256sum {staging_path}/firmware.bin\"\n    result = subprocess.run(download_cmd, shell=True, capture_output=True, text=True)\n    if result.returncode == 0:\n        flash_cmd = f\"iot-flasher --device {device_mac} --binary {staging_path}/firmware.bin --verify\"\n        subprocess.run(flash_cmd, shell=True)\n        return {\"status\": \"provisioned\", \"checksum\": result.stdout.strip()}\n    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":"import subprocess\nimport re\nimport os\n\ndef provision_iot_device(device_mac, firmware_url):\n    # Validate MAC address format strictly\n    if not re.match(r'^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$', device_mac):\n        return {\"status\": \"failed\", \"error\": \"Invalid MAC address format\"}\n    \n    # Validate firmware URL (allow only http/https with safe characters)\n    if not re.match(r'^https?://[a-zA-Z0-9.\\-_/]+$', firmware_url):\n        return {\"status\": \"failed\", \"error\": \"Invalid firmware URL format\"}\n    \n    device_id = device_mac.replace(':', '').lower()\n    staging_path = f\"/opt/iot/staging/{device_id}\"\n    \n    # Ensure staging directory exists\n    os.makedirs(staging_path, exist_ok=True)\n    \n    firmware_path = os.path.join(staging_path, \"firmware.bin\")\n    \n    # Download firmware using subprocess without shell=True\n    download_result = subprocess.run(\n        [\"curl\", \"-o\", firmware_path, \"--\", firmware_url],\n        capture_output=True, text=True\n    )\n    if download_result.returncode != 0:\n        return {\"status\": \"failed\", \"error\": download_result.stderr}\n    \n    # Compute checksum without shell=True\n    checksum_result = subprocess.run(\n        [\"sha256sum\", firmware_path],\n        capture_output=True, text=True\n    )\n    if checksum_result.returncode != 0:\n        return {\"status\": \"failed\", \"error\": checksum_result.stderr}\n    \n    # Flash the device without shell=True\n    flash_result = subprocess.run(\n        [\"iot-flasher\", \"--device\", device_mac, \"--binary\", firmware_path, \"--verify\"],\n        capture_output=True, text=True\n    )\n    if flash_result.returncode == 0:\n        return {\"status\": \"provisioned\", \"checksum\": checksum_result.stdout.strip()}\n    \n    return {\"status\": \"failed\", \"error\": flash_result.stderr}"}