# Import Hijacking via sys.path Precedence

Language: Python
Severity: Critical
CWE: CWE-427

## Source
4

## Flow
4-5-6

## Sink
5

## Vulnerable Code
```python
import sys
import os
def load_cloud_credentials(tenant_id):
    plugin_dir = f"/var/app/tenants/{tenant_id}/plugins"
    sys.path.insert(0, plugin_dir)
    import boto3
    s3_client = boto3.client('s3', aws_access_key_id=os.getenv('AWS_KEY'), aws_secret_access_key=os.getenv('AWS_SECRET'))
    return s3_client
def fetch_tenant_data(tenant_id, bucket_name):
    client = load_cloud_credentials(tenant_id)
    response = client.list_objects_v2(Bucket=bucket_name)
    return response['Contents']
```

## Explanation

The code inserts a user-controlled tenant directory at the beginning of sys.path, then imports boto3. An attacker can place a malicious boto3.py file in their tenant's plugin directory, which will be imported instead of the legitimate library, allowing them to intercept AWS credentials passed to boto3.client() and exfiltrate secrets.

## Remediation

The fix removes the dangerous sys.path.insert() call with a tenant-controlled directory and instead imports boto3 at module level where it cannot be hijacked. For tenant plugins, a dedicated safe loading function uses importlib.util.spec_from_file_location to load only explicitly allowed plugin modules by full path without ever modifying sys.path, preventing any import hijacking.

## Secure Code
```python
import sys
import os
import importlib
import boto3


def load_tenant_plugin(tenant_id, plugin_name):
    """Safely load a tenant plugin without affecting standard library imports."""
    plugin_dir = f"/var/app/tenants/{tenant_id}/plugins"
    
    # Validate tenant_id to prevent path traversal
    if not tenant_id.isalnum():
        raise ValueError("Invalid tenant_id: must be alphanumeric")
    
    if not os.path.isdir(plugin_dir):
        raise FileNotFoundError(f"Plugin directory not found for tenant {tenant_id}")
    
    # Only allow loading of explicitly approved plugin modules
    ALLOWED_PLUGINS = {'data_processor', 'transform', 'formatter'}
    if plugin_name not in ALLOWED_PLUGINS:
        raise ValueError(f"Plugin '{plugin_name}' is not in the allowed plugins list")
    
    plugin_path = os.path.join(plugin_dir, f"{plugin_name}.py")
    plugin_path = os.path.realpath(plugin_path)
    
    # Ensure resolved path is still within the expected plugin directory
    if not plugin_path.startswith(os.path.realpath(plugin_dir)):
        raise ValueError("Path traversal detected in plugin loading")
    
    if not os.path.isfile(plugin_path):
        raise FileNotFoundError(f"Plugin file not found: {plugin_name}")
    
    # Use importlib to load the specific plugin file without modifying sys.path
    spec = importlib.util.spec_from_file_location(f"tenant_plugins.{tenant_id}.{plugin_name}", plugin_path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


def load_cloud_credentials(tenant_id):
    """Load AWS credentials and create S3 client safely without modifying sys.path."""
    # boto3 is imported at module level from the legitimate package
    s3_client = boto3.client(
        's3',
        aws_access_key_id=os.getenv('AWS_KEY'),
        aws_secret_access_key=os.getenv('AWS_SECRET')
    )
    return s3_client


def fetch_tenant_data(tenant_id, bucket_name):
    client = load_cloud_credentials(tenant_id)
    response = client.list_objects_v2(Bucket=bucket_name)
    return response.get('Contents', [])
```
