{"title":"Import Hijacking via Malicious Module Shadowing","language":"Python","severity":"Critical","cwe":"CWE-427","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"import sys\nimport os\n\ndef provision_iot_device(device_id, firmware_path):\n    sys.path.insert(0, os.path.join('/var/iot/devices', device_id, 'modules'))\n    import crypto_utils\n    import device_manager\n    signing_key = crypto_utils.load_private_key('device_signing.pem')\n    firmware_data = open(firmware_path, 'rb').read()\n    signature = crypto_utils.sign_data(signing_key, firmware_data)\n    device_manager.flash_firmware(device_id, firmware_data, signature)\n    return {'status': 'provisioned', 'device': device_id, 'signature': signature.hex()}","explanation":"The function accepts an untrusted device_id parameter and directly inserts a path constructed from it into sys.path without validation. This allows an attacker to control the module search path, enabling them to place malicious crypto_utils.py or device_manager.py modules that will be imported instead of legitimate ones, potentially exfiltrating cryptographic keys or injecting backdoored firmware.","remediation":"The fix eliminates the sys.path manipulation with untrusted input entirely. Instead, modules are loaded exclusively from a trusted, controlled directory using importlib.util.spec_from_file_location with explicit absolute paths. Additionally, the device_id is validated against a strict allowlist pattern to prevent path traversal, and firmware_path is resolved and checked to ensure it resides within the expected directory.","secure_code":"import sys\nimport os\nimport importlib\nimport re\n\n# Trusted module directory where verified modules are installed\nTRUSTED_MODULES_DIR = '/var/iot/trusted_modules'\nALLOWED_DEVICE_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_\\-]+$')\n\ndef provision_iot_device(device_id, firmware_path):\n    # Validate device_id to prevent path traversal\n    if not device_id or not ALLOWED_DEVICE_ID_PATTERN.match(device_id):\n        raise ValueError(f\"Invalid device_id: must be alphanumeric with hyphens/underscores only\")\n    \n    # Validate firmware_path exists and is within expected directory\n    firmware_path = os.path.realpath(firmware_path)\n    if not firmware_path.startswith('/var/iot/firmware/'):\n        raise ValueError(f\"Firmware path must be within /var/iot/firmware/ directory\")\n    \n    # Load crypto_utils and device_manager from a trusted, controlled directory\n    # Do NOT modify sys.path with untrusted input\n    crypto_utils_path = os.path.join(TRUSTED_MODULES_DIR, 'crypto_utils.py')\n    device_manager_path = os.path.join(TRUSTED_MODULES_DIR, 'device_manager.py')\n    \n    if not os.path.isfile(crypto_utils_path):\n        raise ImportError(f\"Trusted crypto_utils module not found at {crypto_utils_path}\")\n    if not os.path.isfile(device_manager_path):\n        raise ImportError(f\"Trusted device_manager module not found at {device_manager_path}\")\n    \n    # Load modules from the trusted directory using importlib with explicit paths\n    spec_crypto = importlib.util.spec_from_file_location('crypto_utils', crypto_utils_path)\n    crypto_utils = importlib.util.module_from_spec(spec_crypto)\n    spec_crypto.loader.exec_module(crypto_utils)\n    \n    spec_device = importlib.util.spec_from_file_location('device_manager', device_manager_path)\n    device_manager = importlib.util.module_from_spec(spec_device)\n    spec_device.loader.exec_module(device_manager)\n    \n    # Load device-specific configuration from a safe data file (not executable code)\n    device_config_path = os.path.join('/var/iot/devices', device_id, 'config.json')\n    device_config_real = os.path.realpath(device_config_path)\n    if not device_config_real.startswith('/var/iot/devices/'):\n        raise ValueError(f\"Device config path traversal detected\")\n    \n    signing_key = crypto_utils.load_private_key('device_signing.pem')\n    with open(firmware_path, 'rb') as f:\n        firmware_data = f.read()\n    signature = crypto_utils.sign_data(signing_key, firmware_data)\n    device_manager.flash_firmware(device_id, firmware_data, signature)\n    return {'status': 'provisioned', 'device': device_id, 'signature': signature.hex()}"}