{"title":"Module Hijacking via `sitecustomize.py` on `sys.path`","language":"Python","severity":"Critical","cwe":"CWE-427","source_lines":[3],"flow_lines":[3,4,5],"sink_lines":[5],"vulnerable_code":"import sys\nimport os\ncloud_config_path = os.getenv('AWS_LAMBDA_CONFIG_DIR', '/tmp/lambda_configs')\nif cloud_config_path not in sys.path:\n    sys.path.insert(0, cloud_config_path)\nimport boto3\nimport sitecustomize\ndef deploy_serverless_function(func_name, runtime_env, iam_role):\n    s3_client = boto3.client('s3')\n    lambda_client = boto3.client('lambda')\n    deployment_pkg = sitecustomize.prepare_deployment_package(func_name, runtime_env)\n    bucket_name = sitecustomize.get_deployment_bucket()\n    s3_client.upload_file(deployment_pkg, bucket_name, f'{func_name}.zip')\n    response = lambda_client.create_function(FunctionName=func_name, Runtime=runtime_env, Role=iam_role, Handler='index.handler', Code={'S3Bucket': bucket_name, 'S3Key': f'{func_name}.zip'})\n    return response['FunctionArn']","explanation":"The code reads an untrusted environment variable AWS_LAMBDA_CONFIG_DIR and inserts it at the beginning of sys.path without validation. An attacker controlling this environment variable can place a malicious sitecustomize.py in that directory, which will be automatically imported by Python and executed with full privileges, allowing credential theft, code injection into Lambda packages, or IAM role exfiltration.","remediation":"The fix eliminates the module hijacking vulnerability by: (1) removing the use of an untrusted environment variable to modify sys.path, replacing it with a hardcoded allowed directory; (2) using importlib to explicitly load a specifically-named module from a validated path instead of importing `sitecustomize` which has special Python semantics; (3) adding file ownership and permission checks to ensure the loaded module hasn't been tampered with by an unprivileged attacker.","secure_code":"import sys\nimport os\nimport importlib.util\n\n# Use a hardcoded, secure path for configuration instead of trusting environment variables\nALLOWED_CONFIG_DIR = '/opt/lambda_configs'\n\ndef _load_deployment_module(module_path):\n    \"\"\"Safely load the deployment helper module from a trusted, validated path.\"\"\"\n    expected_module = os.path.join(module_path, 'deployment_helper.py')\n    \n    # Validate the path is the expected hardcoded directory\n    real_path = os.path.realpath(expected_module)\n    if not real_path.startswith(os.path.realpath(ALLOWED_CONFIG_DIR) + os.sep):\n        raise RuntimeError(f\"Module path {real_path} is outside the allowed config directory\")\n    \n    if not os.path.isfile(real_path):\n        raise FileNotFoundError(f\"Deployment helper module not found at {real_path}\")\n    \n    # Validate file ownership and permissions (must be owned by root, not world-writable)\n    file_stat = os.stat(real_path)\n    if file_stat.st_uid != 0:\n        raise PermissionError(f\"Deployment helper module must be owned by root, owned by uid {file_stat.st_uid}\")\n    if file_stat.st_mode & 0o002:\n        raise PermissionError(\"Deployment helper module must not be world-writable\")\n    \n    # Load the module explicitly using importlib rather than manipulating sys.path\n    spec = importlib.util.spec_from_file_location(\"deployment_helper\", real_path)\n    module = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(module)\n    return module\n\nimport boto3\n\ndef deploy_serverless_function(func_name, runtime_env, iam_role):\n    # Load the deployment helper module from the trusted path\n    deployment_helper = _load_deployment_module(ALLOWED_CONFIG_DIR)\n    \n    s3_client = boto3.client('s3')\n    lambda_client = boto3.client('lambda')\n    \n    deployment_pkg = deployment_helper.prepare_deployment_package(func_name, runtime_env)\n    bucket_name = deployment_helper.get_deployment_bucket()\n    \n    s3_client.upload_file(deployment_pkg, bucket_name, f'{func_name}.zip')\n    \n    response = lambda_client.create_function(\n        FunctionName=func_name,\n        Runtime=runtime_env,\n        Role=iam_role,\n        Handler='index.handler',\n        Code={'S3Bucket': bucket_name, 'S3Key': f'{func_name}.zip'}\n    )\n    return response['FunctionArn']"}