{"title":"Import Hijacking via Untrusted sys.path Manipulation","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[4,12],"flow_lines":[4,5,6,7,12,13,4,5,6,7],"sink_lines":[7],"vulnerable_code":"import sys\nimport os\n\ndef load_cloud_storage_plugin(tenant_id, plugin_dir):\n    storage_path = os.path.join('/var/cloud/tenants', tenant_id, plugin_dir)\n    if os.path.exists(storage_path):\n        sys.path.insert(0, storage_path)\n    import storage_adapter\n    return storage_adapter.get_client()\n\ndef sync_tenant_data(tenant_id, custom_module_path):\n    client = load_cloud_storage_plugin(tenant_id, custom_module_path)\n    data = client.fetch_all()\n    return data","explanation":"The application accepts user-controlled input (tenant_id and plugin_dir) and uses it to construct a file path that is inserted into sys.path. This allows an attacker to control which directory Python searches for modules, enabling them to place a malicious storage_adapter.py in a predictable location that will be imported instead of the legitimate module.","remediation":"The fix validates tenant_id and plugin_dir using strict allowlist regex patterns to prevent path traversal characters, canonicalizes the resulting path and verifies it stays within the expected base directory, and uses importlib.util to load the module from a specific file path instead of manipulating sys.path which could affect other imports globally.","secure_code":"import sys\nimport os\nimport importlib.util\nimport re\n\nALLOWED_TENANT_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_\\-]+$')\nALLOWED_PLUGIN_DIR_PATTERN = re.compile(r'^[a-zA-Z0-9_\\-]+$')\nBASE_TENANT_PATH = '/var/cloud/tenants'\n\ndef load_cloud_storage_plugin(tenant_id, plugin_dir):\n    # Validate tenant_id and plugin_dir to prevent path traversal\n    if not ALLOWED_TENANT_ID_PATTERN.match(tenant_id):\n        raise ValueError(f\"Invalid tenant_id: {tenant_id}\")\n    if not ALLOWED_PLUGIN_DIR_PATTERN.match(plugin_dir):\n        raise ValueError(f\"Invalid plugin_dir: {plugin_dir}\")\n\n    # Construct and canonicalize the path\n    storage_path = os.path.realpath(os.path.join(BASE_TENANT_PATH, tenant_id, plugin_dir))\n\n    # Verify the resolved path is still under the expected base directory\n    expected_prefix = os.path.realpath(BASE_TENANT_PATH)\n    if not storage_path.startswith(expected_prefix + os.sep):\n        raise ValueError(f\"Path escapes tenant directory: {storage_path}\")\n\n    # Load the specific module file directly instead of manipulating sys.path\n    module_file = os.path.join(storage_path, 'storage_adapter.py')\n    if not os.path.isfile(module_file):\n        raise FileNotFoundError(f\"Storage adapter not found at: {module_file}\")\n\n    # Use importlib to load from a specific file path without modifying sys.path\n    spec = importlib.util.spec_from_file_location(\"storage_adapter\", module_file)\n    if spec is None or spec.loader is None:\n        raise ImportError(f\"Cannot load module from: {module_file}\")\n\n    storage_adapter = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(storage_adapter)\n\n    if not hasattr(storage_adapter, 'get_client'):\n        raise AttributeError(\"storage_adapter module missing 'get_client' function\")\n\n    return storage_adapter.get_client()\n\ndef sync_tenant_data(tenant_id, custom_module_path):\n    client = load_cloud_storage_plugin(tenant_id, custom_module_path)\n    data = client.fetch_all()\n    return data"}