# Import Hijacking via sys.path Precedence Abuse

Language: Python
Severity: Critical
CWE: CWE-426

## Source
4-5

## Flow
4-5-6

## Sink
6

## Vulnerable Code
```python
import sys
import os
def load_cloud_credentials(tenant_id):
    plugin_dir = f"/var/app/tenants/{tenant_id}/plugins"
    if os.path.exists(plugin_dir):
        sys.path.insert(0, plugin_dir)
    import azure_auth
    return azure_auth.get_token()
def authenticate_service(tenant):
    creds = load_cloud_credentials(tenant)
    return creds
```

## Explanation

The code inserts a tenant-controlled directory at the highest precedence in sys.path (index 0), then imports 'azure_auth' without validation. A malicious tenant can upload a crafted azure_auth.py to their plugin directory, which will be imported instead of the legitimate module, enabling arbitrary code execution with the application's privileges.

## Remediation

The fix eliminates sys.path manipulation entirely and instead uses importlib.util.spec_from_file_location to load tenant plugins from explicit file paths, preventing import hijacking. It adds integrity verification by checking plugin file SHA-256 hashes against an administrator-maintained approved registry, and includes path traversal prevention through tenant_id validation and symlink resolution checks.

## Secure Code
```python
import sys
import os
import importlib
import importlib.util
import hashlib
import re

# Whitelist of allowed module files with their expected SHA-256 hashes
# This should be maintained by administrators and stored securely
APPROVED_PLUGIN_HASHES_FILE = "/var/app/config/approved_plugin_hashes.json"

def _load_approved_hashes():
    """Load the registry of approved plugin file hashes."""
    import json
    if not os.path.exists(APPROVED_PLUGIN_HASHES_FILE):
        return {}
    with open(APPROVED_PLUGIN_HASHES_FILE, 'r') as f:
        return json.load(f)

def _compute_file_hash(filepath):
    """Compute SHA-256 hash of a file."""
    sha256 = hashlib.sha256()
    with open(filepath, 'rb') as f:
        for block in iter(lambda: f.read(4096), b''):
            sha256.update(block)
    return sha256.hexdigest()

def _validate_tenant_id(tenant_id):
    """Validate tenant_id to prevent path traversal."""
    if not re.match(r'^[a-zA-Z0-9_-]+$', tenant_id):
        raise ValueError(f"Invalid tenant_id: {tenant_id}")
    return tenant_id

def _load_module_safely(module_name, module_path):
    """Load a module from a specific file path without modifying sys.path."""
    spec = importlib.util.spec_from_file_location(module_name, module_path)
    if spec is None:
        raise ImportError(f"Cannot load module spec from {module_path}")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module

def load_cloud_credentials(tenant_id):
    """Load Azure credentials using tenant-specific plugin if approved."""
    tenant_id = _validate_tenant_id(tenant_id)
    plugin_dir = f"/var/app/tenants/{tenant_id}/plugins"
    plugin_file = os.path.join(plugin_dir, "azure_auth.py")
    
    # Resolve to real path to prevent symlink attacks
    if os.path.exists(plugin_file):
        real_path = os.path.realpath(plugin_file)
        expected_prefix = os.path.realpath(f"/var/app/tenants/{tenant_id}/plugins")
        
        # Ensure the resolved path is still within the expected directory
        if not real_path.startswith(expected_prefix + os.sep) and real_path != os.path.join(expected_prefix, "azure_auth.py"):
            raise SecurityError(f"Plugin file path escapes tenant directory: {real_path}")
        
        # Verify file integrity against approved hashes
        approved_hashes = _load_approved_hashes()
        file_hash = _compute_file_hash(real_path)
        tenant_key = f"{tenant_id}/azure_auth.py"
        
        if tenant_key not in approved_hashes:
            raise SecurityError(f"Plugin not in approved registry for tenant: {tenant_id}")
        
        if file_hash != approved_hashes[tenant_key]:
            raise SecurityError(f"Plugin file hash mismatch for tenant: {tenant_id}. File may have been tampered with.")
        
        # Load module from specific path without modifying sys.path
        azure_auth = _load_module_safely(f"tenant_{tenant_id}_azure_auth", real_path)
    else:
        # Fall back to the default azure_auth module from the application's own packages
        import azure_auth
    
    return azure_auth.get_token()


class SecurityError(Exception):
    """Raised when a security validation fails."""
    pass


def authenticate_service(tenant):
    creds = load_cloud_credentials(tenant)
    return creds
```
