{"title":"Command Injection via subprocess.shell=True","language":"Python","severity":"Critical","cwe":"CWE-78","source_lines":[3],"flow_lines":[3,4],"sink_lines":[4],"vulnerable_code":"import subprocess\n\ndef provision_iot_device(device_mac, firmware_url):\n    cmd = f\"curl -o /tmp/{device_mac}.bin {firmware_url} && md5sum /tmp/{device_mac}.bin\"\n    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)\n    if result.returncode == 0:\n        return {\"status\": \"success\", \"checksum\": result.stdout.strip()}\n    return {\"status\": \"failed\", \"error\": result.stderr}","explanation":"The function constructs a shell command using f-string interpolation with unsanitized user inputs (device_mac and firmware_url), then executes it via subprocess.run() with shell=True. An attacker can inject arbitrary shell commands through specially crafted parameters containing shell metacharacters like semicolons, pipes, or backticks.","remediation":"The fix eliminates command injection by: (1) validating device_mac against a strict regex pattern for MAC addresses, (2) validating the firmware URL scheme, (3) using subprocess.run with a list of arguments instead of shell=True which prevents shell metacharacter interpretation, and (4) replacing the md5sum shell command with Python's hashlib for checksum calculation.","secure_code":"import subprocess\nimport re\nimport os\nimport hashlib\nimport urllib.request\nimport urllib.parse\n\n\ndef provision_iot_device(device_mac, firmware_url):\n    # Validate MAC address format (only allow hex digits and colons/hyphens)\n    if not re.match(r'^[0-9A-Fa-f]{2}([:\\-][0-9A-Fa-f]{2}){5}$', device_mac):\n        return {\"status\": \"failed\", \"error\": \"Invalid MAC address format\"}\n\n    # Validate firmware URL scheme\n    parsed_url = urllib.parse.urlparse(firmware_url)\n    if parsed_url.scheme not in ('http', 'https'):\n        return {\"status\": \"failed\", \"error\": \"Invalid firmware URL scheme\"}\n\n    # Sanitize MAC for use as filename (replace colons/hyphens with underscores)\n    safe_mac = device_mac.replace(':', '_').replace('-', '_')\n    output_path = os.path.join('/tmp', f\"{safe_mac}.bin\")\n\n    # Download firmware using subprocess without shell=True\n    try:\n        download_result = subprocess.run(\n            ['curl', '-o', output_path, '--', firmware_url],\n            capture_output=True, text=True, timeout=120\n        )\n        if download_result.returncode != 0:\n            return {\"status\": \"failed\", \"error\": download_result.stderr}\n\n        # Calculate MD5 checksum using Python's hashlib instead of shell command\n        md5_hash = hashlib.md5()\n        with open(output_path, 'rb') as f:\n            for chunk in iter(lambda: f.read(8192), b''):\n                md5_hash.update(chunk)\n        checksum = md5_hash.hexdigest()\n\n        return {\"status\": \"success\", \"checksum\": f\"{checksum}  {output_path}\"}\n    except subprocess.TimeoutExpired:\n        return {\"status\": \"failed\", \"error\": \"Download timed out\"}\n    except FileNotFoundError:\n        return {\"status\": \"failed\", \"error\": \"curl not found or file not downloaded\"}\n    except Exception as e:\n        return {\"status\": \"failed\", \"error\": str(e)}"}