{"title":"Import Hijacking via sys.path Precedence","language":"Python","severity":"Critical","cwe":"CWE-427","source_lines":[4],"flow_lines":[4,5,6],"sink_lines":[5],"vulnerable_code":"import sys\nimport os\ndef load_cloud_credentials(tenant_id):\n    plugin_dir = f\"/var/app/tenants/{tenant_id}/plugins\"\n    sys.path.insert(0, plugin_dir)\n    import boto3\n    s3_client = boto3.client('s3', aws_access_key_id=os.getenv('AWS_KEY'), aws_secret_access_key=os.getenv('AWS_SECRET'))\n    return s3_client\ndef fetch_tenant_data(tenant_id, bucket_name):\n    client = load_cloud_credentials(tenant_id)\n    response = client.list_objects_v2(Bucket=bucket_name)\n    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":"import sys\nimport os\nimport importlib\nimport boto3\n\n\ndef load_tenant_plugin(tenant_id, plugin_name):\n    \"\"\"Safely load a tenant plugin without affecting standard library imports.\"\"\"\n    plugin_dir = f\"/var/app/tenants/{tenant_id}/plugins\"\n    \n    # Validate tenant_id to prevent path traversal\n    if not tenant_id.isalnum():\n        raise ValueError(\"Invalid tenant_id: must be alphanumeric\")\n    \n    if not os.path.isdir(plugin_dir):\n        raise FileNotFoundError(f\"Plugin directory not found for tenant {tenant_id}\")\n    \n    # Only allow loading of explicitly approved plugin modules\n    ALLOWED_PLUGINS = {'data_processor', 'transform', 'formatter'}\n    if plugin_name not in ALLOWED_PLUGINS:\n        raise ValueError(f\"Plugin '{plugin_name}' is not in the allowed plugins list\")\n    \n    plugin_path = os.path.join(plugin_dir, f\"{plugin_name}.py\")\n    plugin_path = os.path.realpath(plugin_path)\n    \n    # Ensure resolved path is still within the expected plugin directory\n    if not plugin_path.startswith(os.path.realpath(plugin_dir)):\n        raise ValueError(\"Path traversal detected in plugin loading\")\n    \n    if not os.path.isfile(plugin_path):\n        raise FileNotFoundError(f\"Plugin file not found: {plugin_name}\")\n    \n    # Use importlib to load the specific plugin file without modifying sys.path\n    spec = importlib.util.spec_from_file_location(f\"tenant_plugins.{tenant_id}.{plugin_name}\", plugin_path)\n    module = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(module)\n    return module\n\n\ndef load_cloud_credentials(tenant_id):\n    \"\"\"Load AWS credentials and create S3 client safely without modifying sys.path.\"\"\"\n    # boto3 is imported at module level from the legitimate package\n    s3_client = boto3.client(\n        's3',\n        aws_access_key_id=os.getenv('AWS_KEY'),\n        aws_secret_access_key=os.getenv('AWS_SECRET')\n    )\n    return s3_client\n\n\ndef fetch_tenant_data(tenant_id, bucket_name):\n    client = load_cloud_credentials(tenant_id)\n    response = client.list_objects_v2(Bucket=bucket_name)\n    return response.get('Contents', [])"}