{"title":"Command Injection via subprocess.shell=True","language":"Python","severity":"Critical","cwe":"CWE-78","source_lines":[3],"flow_lines":[3,4,5,3,7],"sink_lines":[5,7],"vulnerable_code":"import subprocess\n\ndef provision_iot_device(device_mac, firmware_url):\n    log_path = f\"/var/log/iot/provision_{device_mac}.log\"\n    cmd = f\"wget {firmware_url} -O /tmp/firmware.bin && sha256sum /tmp/firmware.bin >> {log_path}\"\n    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)\n    if result.returncode == 0:\n        flash_cmd = f\"iot-flasher --device {device_mac} --firmware /tmp/firmware.bin\"\n        subprocess.run(flash_cmd, shell=True)\n        return {\"status\": \"provisioned\", \"device\": device_mac}\n    return {\"status\": \"failed\", \"error\": result.stderr}","explanation":"The function accepts untrusted input (device_mac and firmware_url) which is directly interpolated into shell commands executed via subprocess.run() with shell=True. An attacker can inject arbitrary shell commands through either parameter, leading to remote code execution.","remediation":"The fix eliminates command injection by replacing all shell=True subprocess calls with argument lists (shell=False), which prevents shell metacharacter interpretation. Input validation is added to ensure device_mac conforms strictly to MAC address format and firmware_url uses only allowed schemes without shell-sensitive characters. File I/O for logging is handled via Python's built-in open() instead of shell redirection, and path traversal is prevented by resolving and validating the log path.","secure_code":"import subprocess\nimport re\nimport os\nfrom urllib.parse import urlparse\n\ndef validate_mac_address(device_mac):\n    \"\"\"Validate that the input is a properly formatted MAC address.\"\"\"\n    mac_pattern = re.compile(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$')\n    if not mac_pattern.match(device_mac):\n        raise ValueError(f\"Invalid MAC address format: {device_mac}\")\n    return device_mac\n\ndef validate_firmware_url(firmware_url):\n    \"\"\"Validate that the firmware URL uses an allowed scheme and has a valid structure.\"\"\"\n    parsed = urlparse(firmware_url)\n    if parsed.scheme not in ('http', 'https'):\n        raise ValueError(f\"Invalid URL scheme: {parsed.scheme}. Only http and https are allowed.\")\n    if not parsed.netloc:\n        raise ValueError(\"Invalid URL: missing hostname.\")\n    if any(c in firmware_url for c in [';', '|', '&', '`', '$', '(', ')', '{', '}', '\\n', '\\r']):\n        raise ValueError(\"URL contains disallowed characters.\")\n    return firmware_url\n\ndef provision_iot_device(device_mac, firmware_url):\n    device_mac = validate_mac_address(device_mac)\n    firmware_url = validate_firmware_url(firmware_url)\n\n    firmware_path = \"/tmp/firmware.bin\"\n    log_path = f\"/var/log/iot/provision_{device_mac}.log\"\n\n    log_dir = os.path.dirname(log_path)\n    resolved_log_path = os.path.realpath(log_path)\n    if not resolved_log_path.startswith(\"/var/log/iot/\"):\n        raise ValueError(\"Invalid log path.\")\n\n    download_result = subprocess.run(\n        [\"wget\", firmware_url, \"-O\", firmware_path],\n        capture_output=True, text=True\n    )\n\n    if download_result.returncode != 0:\n        return {\"status\": \"failed\", \"error\": download_result.stderr}\n\n    hash_result = subprocess.run(\n        [\"sha256sum\", firmware_path],\n        capture_output=True, text=True\n    )\n\n    if hash_result.returncode == 0:\n        with open(resolved_log_path, \"a\") as log_file:\n            log_file.write(hash_result.stdout)\n\n        flash_result = subprocess.run(\n            [\"iot-flasher\", \"--device\", device_mac, \"--firmware\", firmware_path],\n            capture_output=True, text=True\n        )\n\n        if flash_result.returncode == 0:\n            return {\"status\": \"provisioned\", \"device\": device_mac}\n        else:\n            return {\"status\": \"failed\", \"error\": flash_result.stderr}\n\n    return {\"status\": \"failed\", \"error\": hash_result.stderr}"}