{"title":"Command Injection via subprocess.shell=True","language":"Python","severity":"Critical","cwe":"CWE-78","source_lines":[2],"flow_lines":[2,3,4,2,6],"sink_lines":[4,6],"vulnerable_code":"import subprocess\n\ndef provision_iot_device(device_mac, firmware_url):\n    validation_cmd = f\"ping -c 2 {device_mac.replace(':', '-')}.local && curl -o /tmp/fw.bin {firmware_url}\"\n    result = subprocess.run(validation_cmd, shell=True, capture_output=True, text=True)\n    if result.returncode == 0:\n        flash_cmd = f\"iot-flasher --device {device_mac} --firmware /tmp/fw.bin\"\n        flash_result = subprocess.run(flash_cmd, shell=True, capture_output=True)\n        return {\"status\": \"provisioned\", \"output\": flash_result.stdout}\n    return {\"status\": \"failed\", \"error\": result.stderr}","explanation":"The function accepts untrusted input parameters (device_mac and firmware_url) and directly interpolates them into shell commands executed via subprocess.run() with shell=True. This allows attackers to inject arbitrary shell commands through specially crafted MAC addresses or URLs that break out of the intended command structure.","remediation":"The fix eliminates command injection by using subprocess.run() with argument lists instead of shell=True, which prevents shell interpretation of special characters. Additionally, strict input validation ensures that device_mac matches a valid MAC address regex pattern and firmware_url is a properly formed HTTP/HTTPS URL, providing defense-in-depth. The chained shell command is split into separate subprocess calls, each with its own error handling.","secure_code":"import subprocess\nimport re\nfrom urllib.parse import urlparse\n\ndef validate_mac_address(mac):\n    \"\"\"Validate that the input is a properly formatted MAC address.\"\"\"\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_url(url):\n    \"\"\"Validate that the firmware URL is a proper HTTP/HTTPS URL.\"\"\"\n    parsed = urlparse(url)\n    if parsed.scheme not in ('http', 'https'):\n        raise ValueError(f\"Invalid firmware URL scheme: {url}\")\n    if not parsed.hostname:\n        raise ValueError(f\"Invalid firmware URL hostname: {url}\")\n    return url\n\ndef provision_iot_device(device_mac, firmware_url):\n    # Validate inputs strictly\n    device_mac = validate_mac_address(device_mac)\n    firmware_url = validate_firmware_url(firmware_url)\n\n    # Convert MAC to hostname format for ping\n    device_hostname = device_mac.replace(':', '-') + '.local'\n\n    # Run ping as a separate command without shell=True\n    ping_result = subprocess.run(\n        ['ping', '-c', '2', device_hostname],\n        capture_output=True, text=True\n    )\n    if ping_result.returncode != 0:\n        return {\"status\": \"failed\", \"error\": \"Device not reachable\"}\n\n    # Run curl as a separate command without shell=True\n    curl_result = subprocess.run(\n        ['curl', '-o', '/tmp/fw.bin', '--', firmware_url],\n        capture_output=True, text=True\n    )\n    if curl_result.returncode != 0:\n        return {\"status\": \"failed\", \"error\": curl_result.stderr}\n\n    # Run flash command without shell=True\n    flash_result = subprocess.run(\n        ['iot-flasher', '--device', device_mac, '--firmware', '/tmp/fw.bin'],\n        capture_output=True, text=True\n    )\n    if flash_result.returncode == 0:\n        return {\"status\": \"provisioned\", \"output\": flash_result.stdout}\n    return {\"status\": \"failed\", \"error\": flash_result.stderr}"}