{"title":"Import Hijacking via sys.path Manipulation","language":"Python","severity":"Critical","cwe":"CWE-427","source_lines":[4],"flow_lines":[4,8],"sink_lines":[8],"vulnerable_code":"import sys\nimport os\n\ndef load_cloud_credentials(provider_path):\n    credential_dir = os.path.join('/tmp/cloud_configs', provider_path)\n    if not os.path.exists(credential_dir):\n        os.makedirs(credential_dir)\n    sys.path.insert(0, credential_dir)\n    import aws_auth\n    session = aws_auth.create_session()\n    return session.get_credentials()\n\ndef authenticate_s3_bucket(bucket_name, config_path):\n    creds = load_cloud_credentials(config_path)\n    return creds","explanation":"The function accepts user-controlled `provider_path` and directly constructs a directory path that is inserted at the beginning of sys.path without validation. An attacker can use path traversal (e.g., '../../../attacker_dir') to inject a malicious directory containing a crafted aws_auth.py module, which will be imported and executed with the application's privileges.","remediation":"The fix eliminates sys.path manipulation entirely and instead uses importlib.util to load the module directly from a validated file path. It sanitizes the provider_path input by rejecting path traversal characters and non-alphanumeric names, then verifies the resolved absolute path stays within the allowed base directory before loading any module.","secure_code":"import sys\nimport os\nimport importlib.util\n\nALLOWED_PROVIDERS = None  # Set to a list of allowed provider names to restrict further\nBASE_CREDENTIAL_DIR = '/tmp/cloud_configs'\n\ndef load_cloud_credentials(provider_path):\n    # Sanitize: only allow simple directory names, no path separators or traversal\n    if not provider_path or '..' in provider_path or os.sep in provider_path or '/' in provider_path:\n        raise ValueError(f\"Invalid provider path: '{provider_path}'. Path traversal is not allowed.\")\n    \n    # Only allow alphanumeric characters, hyphens, and underscores\n    if not all(c.isalnum() or c in ('-', '_') for c in provider_path):\n        raise ValueError(f\"Invalid provider path: '{provider_path}'. Only alphanumeric characters, hyphens, and underscores are allowed.\")\n    \n    # If an allowlist is configured, enforce it\n    if ALLOWED_PROVIDERS is not None and provider_path not in ALLOWED_PROVIDERS:\n        raise ValueError(f\"Provider '{provider_path}' is not in the list of allowed providers.\")\n    \n    credential_dir = os.path.join(BASE_CREDENTIAL_DIR, provider_path)\n    \n    # Resolve to absolute path and verify it's still under the base directory\n    credential_dir = os.path.realpath(credential_dir)\n    base_dir_resolved = os.path.realpath(BASE_CREDENTIAL_DIR)\n    if not credential_dir.startswith(base_dir_resolved + os.sep):\n        raise ValueError(f\"Resolved path escapes the allowed base directory.\")\n    \n    if not os.path.exists(credential_dir):\n        os.makedirs(credential_dir, mode=0o750)\n    \n    # Load the module directly from the expected file rather than manipulating sys.path\n    module_file = os.path.join(credential_dir, 'aws_auth.py')\n    if not os.path.isfile(module_file):\n        raise FileNotFoundError(f\"AWS auth module not found at: {module_file}\")\n    \n    # Verify the module file is within the allowed directory\n    module_file_resolved = os.path.realpath(module_file)\n    if not module_file_resolved.startswith(credential_dir + os.sep) and module_file_resolved != os.path.join(credential_dir, 'aws_auth.py'):\n        raise ValueError(\"Module file path escapes the allowed credential directory.\")\n    \n    spec = importlib.util.spec_from_file_location(f\"aws_auth_{provider_path}\", module_file)\n    aws_auth = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(aws_auth)\n    \n    session = aws_auth.create_session()\n    return session.get_credentials()\n\ndef authenticate_s3_bucket(bucket_name, config_path):\n    creds = load_cloud_credentials(config_path)\n    return creds"}