{"title":"Import Hijacking via sys.path Precedence Abuse","language":"Python","severity":"Critical","cwe":"CWE-426","source_lines":[4,5],"flow_lines":[4,5,6],"sink_lines":[6],"vulnerable_code":"import sys\nimport os\ndef load_cloud_credentials(tenant_id):\n    plugin_dir = f\"/var/app/tenants/{tenant_id}/plugins\"\n    if os.path.exists(plugin_dir):\n        sys.path.insert(0, plugin_dir)\n    import azure_auth\n    return azure_auth.get_token()\ndef authenticate_service(tenant):\n    creds = load_cloud_credentials(tenant)\n    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":"import sys\nimport os\nimport importlib\nimport importlib.util\nimport hashlib\nimport re\n\n# Whitelist of allowed module files with their expected SHA-256 hashes\n# This should be maintained by administrators and stored securely\nAPPROVED_PLUGIN_HASHES_FILE = \"/var/app/config/approved_plugin_hashes.json\"\n\ndef _load_approved_hashes():\n    \"\"\"Load the registry of approved plugin file hashes.\"\"\"\n    import json\n    if not os.path.exists(APPROVED_PLUGIN_HASHES_FILE):\n        return {}\n    with open(APPROVED_PLUGIN_HASHES_FILE, 'r') as f:\n        return json.load(f)\n\ndef _compute_file_hash(filepath):\n    \"\"\"Compute SHA-256 hash of a file.\"\"\"\n    sha256 = hashlib.sha256()\n    with open(filepath, 'rb') as f:\n        for block in iter(lambda: f.read(4096), b''):\n            sha256.update(block)\n    return sha256.hexdigest()\n\ndef _validate_tenant_id(tenant_id):\n    \"\"\"Validate tenant_id to prevent path traversal.\"\"\"\n    if not re.match(r'^[a-zA-Z0-9_-]+$', tenant_id):\n        raise ValueError(f\"Invalid tenant_id: {tenant_id}\")\n    return tenant_id\n\ndef _load_module_safely(module_name, module_path):\n    \"\"\"Load a module from a specific file path without modifying sys.path.\"\"\"\n    spec = importlib.util.spec_from_file_location(module_name, module_path)\n    if spec is None:\n        raise ImportError(f\"Cannot load module spec from {module_path}\")\n    module = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(module)\n    return module\n\ndef load_cloud_credentials(tenant_id):\n    \"\"\"Load Azure credentials using tenant-specific plugin if approved.\"\"\"\n    tenant_id = _validate_tenant_id(tenant_id)\n    plugin_dir = f\"/var/app/tenants/{tenant_id}/plugins\"\n    plugin_file = os.path.join(plugin_dir, \"azure_auth.py\")\n    \n    # Resolve to real path to prevent symlink attacks\n    if os.path.exists(plugin_file):\n        real_path = os.path.realpath(plugin_file)\n        expected_prefix = os.path.realpath(f\"/var/app/tenants/{tenant_id}/plugins\")\n        \n        # Ensure the resolved path is still within the expected directory\n        if not real_path.startswith(expected_prefix + os.sep) and real_path != os.path.join(expected_prefix, \"azure_auth.py\"):\n            raise SecurityError(f\"Plugin file path escapes tenant directory: {real_path}\")\n        \n        # Verify file integrity against approved hashes\n        approved_hashes = _load_approved_hashes()\n        file_hash = _compute_file_hash(real_path)\n        tenant_key = f\"{tenant_id}/azure_auth.py\"\n        \n        if tenant_key not in approved_hashes:\n            raise SecurityError(f\"Plugin not in approved registry for tenant: {tenant_id}\")\n        \n        if file_hash != approved_hashes[tenant_key]:\n            raise SecurityError(f\"Plugin file hash mismatch for tenant: {tenant_id}. File may have been tampered with.\")\n        \n        # Load module from specific path without modifying sys.path\n        azure_auth = _load_module_safely(f\"tenant_{tenant_id}_azure_auth\", real_path)\n    else:\n        # Fall back to the default azure_auth module from the application's own packages\n        import azure_auth\n    \n    return azure_auth.get_token()\n\n\nclass SecurityError(Exception):\n    \"\"\"Raised when a security validation fails.\"\"\"\n    pass\n\n\ndef authenticate_service(tenant):\n    creds = load_cloud_credentials(tenant)\n    return creds"}