# Import Hijacking via Malicious Module Shadowing

Language: Python
Severity: Critical
CWE: CWE-427

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
import sys
import os

def provision_iot_device(device_id, firmware_path):
    sys.path.insert(0, os.path.join('/var/iot/devices', device_id, 'modules'))
    import crypto_utils
    import device_manager
    signing_key = crypto_utils.load_private_key('device_signing.pem')
    firmware_data = open(firmware_path, 'rb').read()
    signature = crypto_utils.sign_data(signing_key, firmware_data)
    device_manager.flash_firmware(device_id, firmware_data, signature)
    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
```python
import sys
import os
import importlib
import re

# Trusted module directory where verified modules are installed
TRUSTED_MODULES_DIR = '/var/iot/trusted_modules'
ALLOWED_DEVICE_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_\-]+$')

def provision_iot_device(device_id, firmware_path):
    # Validate device_id to prevent path traversal
    if not device_id or not ALLOWED_DEVICE_ID_PATTERN.match(device_id):
        raise ValueError(f"Invalid device_id: must be alphanumeric with hyphens/underscores only")
    
    # Validate firmware_path exists and is within expected directory
    firmware_path = os.path.realpath(firmware_path)
    if not firmware_path.startswith('/var/iot/firmware/'):
        raise ValueError(f"Firmware path must be within /var/iot/firmware/ directory")
    
    # Load crypto_utils and device_manager from a trusted, controlled directory
    # Do NOT modify sys.path with untrusted input
    crypto_utils_path = os.path.join(TRUSTED_MODULES_DIR, 'crypto_utils.py')
    device_manager_path = os.path.join(TRUSTED_MODULES_DIR, 'device_manager.py')
    
    if not os.path.isfile(crypto_utils_path):
        raise ImportError(f"Trusted crypto_utils module not found at {crypto_utils_path}")
    if not os.path.isfile(device_manager_path):
        raise ImportError(f"Trusted device_manager module not found at {device_manager_path}")
    
    # Load modules from the trusted directory using importlib with explicit paths
    spec_crypto = importlib.util.spec_from_file_location('crypto_utils', crypto_utils_path)
    crypto_utils = importlib.util.module_from_spec(spec_crypto)
    spec_crypto.loader.exec_module(crypto_utils)
    
    spec_device = importlib.util.spec_from_file_location('device_manager', device_manager_path)
    device_manager = importlib.util.module_from_spec(spec_device)
    spec_device.loader.exec_module(device_manager)
    
    # Load device-specific configuration from a safe data file (not executable code)
    device_config_path = os.path.join('/var/iot/devices', device_id, 'config.json')
    device_config_real = os.path.realpath(device_config_path)
    if not device_config_real.startswith('/var/iot/devices/'):
        raise ValueError(f"Device config path traversal detected")
    
    signing_key = crypto_utils.load_private_key('device_signing.pem')
    with open(firmware_path, 'rb') as f:
        firmware_data = f.read()
    signature = crypto_utils.sign_data(signing_key, firmware_data)
    device_manager.flash_firmware(device_id, firmware_data, signature)
    return {'status': 'provisioned', 'device': device_id, 'signature': signature.hex()}
```
