{"title":"Import Hijacking via sys.path Precedence","language":"Python","severity":"Critical","cwe":"CWE-427","source_lines":[3],"flow_lines":[3,4,6],"sink_lines":[6],"vulnerable_code":"import sys\nimport os\ndef load_cloud_credentials(bucket_name):\n    plugin_dir = os.path.join('/var/app/plugins', bucket_name)\n    if os.path.exists(plugin_dir):\n        sys.path.insert(0, plugin_dir)\n    import boto3\n    from aws_config import get_session_token\n    token = get_session_token()\n    s3_client = boto3.client('s3', aws_access_key_id=token['key'], aws_secret_access_key=token['secret'])\n    return s3_client\ndef fetch_encrypted_data(bucket, object_key):\n    client = load_cloud_credentials(bucket)\n    response = client.get_object(Bucket=bucket, Key=object_key)\n    return response['Body'].read()","explanation":"The bucket_name parameter is user-controlled and used to construct a directory path that gets prepended to sys.path. This allows an attacker to inject malicious boto3 or aws_config modules that will be imported before legitimate ones, enabling credential theft or code execution when the imports occur on lines 7-8.","remediation":"The fix removes the dangerous sys.path.insert() call entirely and instead uses top-level imports of boto3 and aws_config, preventing any user-controlled directory from influencing module resolution. Additionally, strict input validation is applied to bucket_name using a regex matching valid S3 bucket naming conventions, and a realpath check ensures the resolved plugin directory cannot escape the approved base path via symlinks or traversal.","secure_code":"import sys\nimport os\nimport re\nimport importlib\nimport boto3\nfrom aws_config import get_session_token\n\nALLOWED_BUCKET_PATTERN = re.compile(r'^[a-z0-9][a-z0-9.\\-]{1,61}[a-z0-9]$')\nAPPROVED_PLUGIN_BASE = '/var/app/plugins'\n\ndef load_cloud_credentials(bucket_name):\n    # Validate bucket_name against AWS S3 bucket naming rules to prevent path traversal\n    if not bucket_name or not ALLOWED_BUCKET_PATTERN.match(bucket_name):\n        raise ValueError(f\"Invalid bucket name: {bucket_name}\")\n    \n    # Ensure no path traversal components\n    if '..' in bucket_name or '/' in bucket_name or '\\\\' in bucket_name:\n        raise ValueError(f\"Invalid bucket name contains path traversal characters: {bucket_name}\")\n    \n    plugin_dir = os.path.join(APPROVED_PLUGIN_BASE, bucket_name)\n    \n    # Resolve to absolute path and verify it's still under the approved base\n    resolved_path = os.path.realpath(plugin_dir)\n    approved_base_resolved = os.path.realpath(APPROVED_PLUGIN_BASE)\n    if not resolved_path.startswith(approved_base_resolved + os.sep):\n        raise ValueError(f\"Plugin directory escapes approved base path: {bucket_name}\")\n    \n    # Do NOT modify sys.path - use top-level imports of boto3 and aws_config instead\n    # If bucket-specific configuration is needed, load only data files (not Python modules)\n    token = get_session_token()\n    s3_client = boto3.client('s3', aws_access_key_id=token['key'], aws_secret_access_key=token['secret'])\n    return s3_client\n\ndef fetch_encrypted_data(bucket, object_key):\n    client = load_cloud_credentials(bucket)\n    response = client.get_object(Bucket=bucket, Key=object_key)\n    return response['Body'].read()"}