{"title":"Import Hijacking via sys.path Manipulation","language":"Python","severity":"Critical","cwe":"CWE-427","source_lines":[13],"flow_lines":[13,5,6,7],"sink_lines":[7],"vulnerable_code":"import sys\nimport os\n\ndef load_cloud_storage_plugin(plugin_dir, bucket_name):\n    plugin_path = os.path.join('/var/app/plugins', plugin_dir)\n    if os.path.exists(plugin_path):\n        sys.path.insert(0, plugin_path)\n    import s3_handler\n    storage = s3_handler.S3Manager(bucket_name)\n    return storage.get_connection()\n\ndef sync_aws_bucket(user_plugin, bucket):\n    conn = load_cloud_storage_plugin(user_plugin, bucket)\n    return conn.list_objects()","explanation":"The application accepts user-controlled input (user_plugin parameter) and uses it to construct a path that is inserted at the beginning of sys.path without validation. This allows an attacker to inject a malicious directory containing a fake s3_handler module that will be imported instead of the legitimate one, leading to arbitrary code execution.","remediation":"The fix eliminates sys.path manipulation entirely and instead uses importlib to load the module from a specific, validated file path. It validates that the plugin directory name contains only safe characters (alphanumeric, underscore, hyphen) to prevent path traversal, and additionally verifies the resolved absolute path remains within the allowed plugins base directory.","secure_code":"import sys\nimport os\nimport importlib.util\nimport re\n\nALLOWED_PLUGINS_BASE = '/var/app/plugins'\nALLOWED_PLUGIN_NAMES = re.compile(r'^[a-zA-Z0-9_\\-]+$')\n\ndef load_cloud_storage_plugin(plugin_dir, bucket_name):\n    # Validate plugin_dir is a simple directory name (no path traversal)\n    if not ALLOWED_PLUGIN_NAMES.match(plugin_dir):\n        raise ValueError(f\"Invalid plugin directory name: {plugin_dir}. Only alphanumeric, underscore, and hyphen characters are allowed.\")\n    \n    plugin_path = os.path.join(ALLOWED_PLUGINS_BASE, plugin_dir)\n    \n    # Resolve to absolute path and verify it's still under the allowed base\n    resolved_path = os.path.realpath(plugin_path)\n    allowed_base_resolved = os.path.realpath(ALLOWED_PLUGINS_BASE)\n    if not resolved_path.startswith(allowed_base_resolved + os.sep):\n        raise ValueError(f\"Plugin path escapes allowed directory: {plugin_dir}\")\n    \n    if not os.path.isdir(resolved_path):\n        raise FileNotFoundError(f\"Plugin directory does not exist: {resolved_path}\")\n    \n    # Load s3_handler module directly from the validated path instead of manipulating sys.path\n    module_file = os.path.join(resolved_path, 's3_handler.py')\n    if not os.path.isfile(module_file):\n        raise FileNotFoundError(f\"s3_handler.py not found in plugin directory: {resolved_path}\")\n    \n    # Use importlib to load the module from a specific file path\n    spec = importlib.util.spec_from_file_location('s3_handler', module_file)\n    if spec is None or spec.loader is None:\n        raise ImportError(f\"Cannot load s3_handler module from: {module_file}\")\n    \n    s3_handler = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(s3_handler)\n    \n    storage = s3_handler.S3Manager(bucket_name)\n    return storage.get_connection()\n\ndef sync_aws_bucket(user_plugin, bucket):\n    conn = load_cloud_storage_plugin(user_plugin, bucket)\n    return conn.list_objects()"}