{"title":"Import Hijacking via sys.meta_path Finder Abuse","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[17],"flow_lines":[17,18,11],"sink_lines":[18,11],"vulnerable_code":"import sys\nimport importlib.abc\nimport importlib.machinery\n\nclass CloudConfigLoader(importlib.abc.MetaPathFinder):\n    def find_module(self, fullname, path=None):\n        if fullname == 'aws_credentials':\n            return CloudModuleLoader()\n        return None\n\nclass CloudModuleLoader(importlib.abc.Loader):\n    def load_module(self, fullname):\n        mod = sys.modules.setdefault(fullname, importlib.util.module_from_spec(importlib.machinery.ModuleSpec(fullname, self)))\n        exec(open('/tmp/cloud_config.py').read(), mod.__dict__)\n        return mod\n\ndef initialize_aws_environment(user_plugin_path):\n    sys.meta_path.insert(0, CloudConfigLoader())\n    exec(open(user_plugin_path).read())\n    import aws_credentials\n    return aws_credentials.get_session()","explanation":"The code accepts a user-controlled file path (user_plugin_path) and executes its contents using exec() without validation. Additionally, the custom import system executes arbitrary code from /tmp/cloud_config.py. An attacker can inject malicious Python code through either the user_plugin_path parameter or by placing malicious code in /tmp/cloud_config.py, leading to arbitrary code execution with the application's privileges.","remediation":"The fix eliminates arbitrary code execution by replacing exec() with importlib-based module loading, enforcing strict path validation to ensure files are within designated allowed directories (not world-writable /tmp), and adding file integrity verification via SHA-256 hash checking against pre-approved plugins. Symlink traversal attacks are also prevented by resolving real paths before validation.","secure_code":"import sys\nimport os\nimport importlib.abc\nimport importlib.machinery\nimport importlib.util\nimport hashlib\n\n# Allowlisted configuration directory (not world-writable)\nALLOWED_CONFIG_DIR = '/etc/cloud_platform/configs'\nALLOWED_PLUGIN_DIR = '/etc/cloud_platform/plugins'\n# Set of allowed plugin file hashes (pre-approved plugins)\nAPPROVED_PLUGIN_HASHES = set()\n\n\ndef _validate_path(file_path, allowed_directory):\n    \"\"\"Validate that a file path is within the allowed directory and exists.\"\"\"\n    if not file_path or not isinstance(file_path, str):\n        raise ValueError(\"Invalid file path provided\")\n    \n    real_path = os.path.realpath(file_path)\n    allowed_real = os.path.realpath(allowed_directory)\n    \n    if not real_path.startswith(allowed_real + os.sep):\n        raise PermissionError(\n            f\"Access denied: path must be within {allowed_directory}\"\n        )\n    \n    if not os.path.isfile(real_path):\n        raise FileNotFoundError(f\"File not found: {real_path}\")\n    \n    # Ensure the file is not a symlink to outside the allowed directory\n    if os.path.islink(file_path):\n        link_target = os.path.realpath(file_path)\n        if not link_target.startswith(allowed_real + os.sep):\n            raise PermissionError(\"Symlinks to outside allowed directory are not permitted\")\n    \n    return real_path\n\n\ndef _verify_file_integrity(file_path):\n    \"\"\"Verify file integrity against approved hashes.\"\"\"\n    with open(file_path, 'rb') as f:\n        file_hash = hashlib.sha256(f.read()).hexdigest()\n    if APPROVED_PLUGIN_HASHES and file_hash not in APPROVED_PLUGIN_HASHES:\n        raise PermissionError(\n            f\"File integrity check failed: {file_path} is not an approved plugin\"\n        )\n    return True\n\n\nclass CloudConfigLoader(importlib.abc.MetaPathFinder):\n    def find_module(self, fullname, path=None):\n        if fullname == 'aws_credentials':\n            return CloudModuleLoader()\n        return None\n\n\nclass CloudModuleLoader(importlib.abc.Loader):\n    def load_module(self, fullname):\n        config_path = _validate_path(\n            os.path.join(ALLOWED_CONFIG_DIR, 'cloud_config.py'),\n            ALLOWED_CONFIG_DIR\n        )\n        _verify_file_integrity(config_path)\n        \n        spec = importlib.machinery.ModuleSpec(fullname, self, origin=config_path)\n        mod = importlib.util.module_from_spec(spec)\n        sys.modules[fullname] = mod\n        \n        with open(config_path, 'r') as f:\n            code = compile(f.read(), config_path, 'exec')\n        exec(code, mod.__dict__)\n        return mod\n\n\ndef initialize_aws_environment(user_plugin_path):\n    \"\"\"Initialize AWS environment with validated plugin loading.\"\"\"\n    # Validate the plugin path is within allowed directory\n    validated_path = _validate_path(user_plugin_path, ALLOWED_PLUGIN_DIR)\n    \n    # Verify plugin file integrity\n    _verify_file_integrity(validated_path)\n    \n    # Register the custom loader\n    sys.meta_path.insert(0, CloudConfigLoader())\n    \n    # Load the plugin module safely using importlib instead of exec\n    spec = importlib.util.spec_from_file_location(\"user_plugin\", validated_path)\n    if spec is None or spec.loader is None:\n        raise ImportError(f\"Cannot load plugin from {validated_path}\")\n    \n    plugin_module = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(plugin_module)\n    \n    import aws_credentials\n    return aws_credentials.get_session()"}