⚡ Serverless Security

Serverless Security Reference

AWS Lambda, Azure Functions, GCP Cloud Functions — attack surface, event injection, IAM, secrets, SSRF, and supply chain threats with detection and mitigation for each.

⚡ Serverless Threat Reference

Event Injection via API Gateway Critical T1190
API Gateway invokes Lambda with the raw event body — if Lambda passes user input to shell commands, SQL queries, or XML parsers without sanitisation, classic injection applies in a serverless context.
Detection
CloudWatch Logs: Lambda error patterns matching injection payloads. WAF: SQLi/XSS pattern matches on API Gateway.
Mitigation
AWS WAF in front of API Gateway. Input validation in Lambda code. Use parameterised queries — never string-format SQL.
Lambda code: NEVER do subprocess.run(f"command {event['body']}") or cursor.execute(f"SELECT * FROM users WHERE id={user_id}")
Event Injection via S3 Trigger Critical T1190
Lambda triggered by S3 PutObject events uses the filename or content in code logic. Attacker uploads a file with a malicious name (e.g., "; rm -rf /tmp/*") or content that triggers code execution.
Detection
CloudWatch Logs: Lambda function errors. S3 access logs: unusual object names. GuardDuty S3 protection.
Mitigation
Sanitise S3 object key before using in any command. Never execute filenames as commands. Process file content safely.
Python: key = event["Records"][0]["s3"]["object"]["key"] — this is attacker-controlled input. urllib.parse.unquote_plus it and validate before use.
Excessive IAM Permissions on Execution Role Critical T1078.004
Lambda execution role has AdministratorAccess or * permissions — if Lambda is exploited (via injection or supply chain), attacker inherits full AWS account access via IMDS/credential endpoint.
Detection
CloudTrail: API calls from Lambda execution role from unexpected operations (IAM writes, EC2 launches, etc.)
Mitigation
Apply least privilege to Lambda execution role. Use resource-based conditions. Lambda should only have exactly the permissions it needs.
aws lambda get-function-configuration --function-name NAME | grep Role — then check that role's policies.
SSRF from Lambda → IMDS Credential Access Critical T1552.005
If Lambda makes HTTP requests to user-supplied URLs (e.g., a URL fetcher, image processor, PDF generator), attacker submits http://169.254.169.254/latest/meta-data/iam/security-credentials/ — gets Lambda execution role credentials.
Detection
CloudWatch Logs: requests to 169.254.x.x from Lambda function. WAF: block SSRF patterns on API Gateway input.
Mitigation
Validate and allowlist URLs in Lambda code. Block private IP ranges in outbound requests. IMDSv2 doesn't help here — Lambda uses its own credential endpoint.
In Lambda: always validate URL schemes and destinations before making outbound HTTP requests.
Environment Variable Secret Exposure High T1552
Secrets (database passwords, API keys) stored in Lambda environment variables are exposed in: AWS console (visible to IAM users with read access), CloudFormation templates, exported function configs, Lambda deployment packages.
Detection
CloudTrail: GetFunctionConfiguration calls (reveals env vars) from unusual identities.
Mitigation
Use Secrets Manager or SSM Parameter Store — reference by name in code, not stored in env vars. Never log os.environ["API_KEY"].
aws lambda get-function-configuration --function-name NAME — anyone with this permission can see all env vars.
Dependency Confusion / Supply Chain Attack High T1195.001
Lambda deployment packages include third-party dependencies. If the package manager pulls a malicious package (typosquatting, namespace confusion, compromised registry), malicious code runs inside Lambda with its execution role permissions.
Detection
Unusual outbound connections from Lambda in CloudWatch Logs or VPC Flow Logs. Lambda execution behavior changes without code deployment.
Mitigation
Pin all dependencies to specific versions with hash verification. Use private package mirrors. Scan with Dependabot or Snyk in CI/CD. SBOMs for all Lambda packages.
requirements.txt or package.json pinned versions are not enough — also verify hashes.
Lambda Layer Tampering High T1195
Lambda Layers shared across functions — if an attacker modifies a shared layer (via IAM permissions on the layer), every function using that layer is compromised on next invocation.
Detection
CloudTrail: PublishLayerVersion from unexpected IAM identity.
Mitigation
Restrict PublishLayerVersion and UpdateFunctionConfiguration to CI/CD pipeline IAM roles only. Sign and verify layer versions.
aws lambda add-layer-version-permission — check who has permission to publish to shared layers.
Cold Start Timing — Sandbox Reuse Information Leak High T1497
Lambda execution environments are reused (warm starts). Residual data from previous invocations (global variables, /tmp filesystem contents) can leak to subsequent invocations of the same function if not properly cleaned.
Detection
Application-layer: look for data leakage in function responses that contains data from other users.
Mitigation
Clear /tmp between invocations. Never store sensitive data in global variables. Treat each invocation as potentially being served by a shared environment.
Python global state is NOT reset between warm invocations — user_data = {} at module level persists.

🔐 Serverless IAM — Least Privilege Reference

Lambda execution role — what to grant

What Lambda NeedsGrant ThisNot This
Write logs to CloudWatchlogs:CreateLogGroup, logs:CreateLogStream, logs:PutLogEventslogs:*
Read from one S3 buckets3:GetObject on arn:aws:s3:::bucket-name/*s3:* on *
Write to one S3 buckets3:PutObject on specific bucket ARN + paths3:*
Read one secretsecretsmanager:GetSecretValue on specific secret ARNsecretsmanager:*
Write to DynamoDB tabledynamodb:PutItem, dynamodb:UpdateItem on table ARNdynamodb:*
Invoke another Lambdalambda:InvokeFunction on specific function ARNlambda:*
Send SQS messagesqs:SendMessage on specific queue ARNsqs:*
VPC access (ENI creation)ec2:CreateNetworkInterface, ec2:DescribeNetworkInterfaces, ec2:DeleteNetworkInterfaceec2:*

Audit Lambda execution roles

#!/bin/bash # Audit all Lambda execution roles for over-privilege for func in $(aws lambda list-functions --query "Functions[*].FunctionName" --output text); do role=$(aws lambda get-function-configuration --function-name "$func" \ --query "Role" --output text) echo -e "\n=== $func ===" echo "Role: $role" # Get attached policies role_name=$(echo $role | sed 's/.*role\///') aws iam list-attached-role-policies --role-name "$role_name" \ --query "AttachedPolicies[*].PolicyName" --output text # Flag if admin policy aws iam list-attached-role-policies --role-name "$role_name" \ --query "AttachedPolicies[?PolicyName=='AdministratorAccess']" --output text | \ grep -q "AdministratorAccess" && echo "⚠️ ADMIN POLICY ATTACHED" done

🔑 Secrets & Configuration Security

Environment variable vs Secrets Manager — comparison

MethodVisible to IAM users with Lambda read?Supports rotation?Audit trail?Recommendation
Env variable (plaintext)Yes — anyone with GetFunctionConfigurationNoNoNever for secrets
Env variable (KMS encrypted)Partial — encrypted but key access requiredNoNoAcceptable for non-critical
AWS Secrets ManagerNo — requires separate IAM permissionYesYes (CloudTrail)Recommended
SSM Parameter Store (SecureString)No — requires separate IAM permissionManualYes (CloudTrail)Good for non-rotating secrets

Secure secrets access pattern in Lambda

import boto3 import json import os from functools import lru_cache # GOOD: Fetch secret once and cache (avoids per-request latency) @lru_cache(maxsize=1) def get_db_credentials(): client = boto3.client('secretsmanager', region_name='ap-south-1') response = client.get_secret_value(SecretId=os.environ['DB_SECRET_ARN']) return json.loads(response['SecretString']) def lambda_handler(event, context): creds = get_db_credentials() db_password = creds['password'] # Use creds - never log them, never put in response body # BAD - never do this: # print(f"Connecting with password: {db_password}") # return {"password": db_password}

📦 Supply Chain Security for Serverless

Dependency hash pinning

# requirements.txt — BAD: version only boto3==1.28.0 # Can be replaced with compromised build # requirements.txt — GOOD: version + hash boto3==1.28.0 \ --hash=sha256:abc123def456... # pip will verify before installing # Generate hashes automatically: pip-compile --generate-hashes requirements.in > requirements.txt # Verify existing requirements: pip install --require-hashes -r requirements.txt # For Node.js: use package-lock.json with integrity field # npm ci # Uses lockfile with integrity hashes — safer than npm install

Common serverless supply chain attack types

Attack TypeExampleProtection
Typosquattingrequsets instead of requests — malicious lookalike packageHash pinning; private package mirror; Dependabot scanning
Namespace confusionPrivate package company-utils served by public PyPI company-utils with higher versionPrivate registry; block public package names matching internal ones
Compromised maintainer accountLegitimate package updated with malicious code by hijacked maintainerHash pinning prevents updates; SBOM tracks exact versions
Malicious Lambda LayerThird-party shared Layer contains backdoor — affects all functions using itOnly use Layers you publish; verify Layer source; pin Layer version ARN
CI/CD pipeline injectionBuild pipeline compromised → malicious code added before deploymentSigned commits; artifact signing; CI/CD pipeline access controls

🔍 Detection & Hardening Checklist

Enable for all Lambda functions

CloudWatch Logs enabled for all Lambda functions
Logs are essential — without them, Lambda is a black box during incidents. Minimum retention: 90 days.
Lambda function URL / API Gateway with authentication
No public unauthenticated Lambda endpoints unless deliberately intended. Use IAM auth or API keys.
Resource-based policy reviewed on all Lambdas
Lambda resource policies control who can invoke the function. Review for * principals.
No * in execution role permissions
Every Lambda should have minimal IAM role. Audit with aws iam simulate-principal-policy.
Secrets in Secrets Manager, not env variables
Rotate all secrets currently in env vars to Secrets Manager.
Lambda deployed inside VPC where it accesses private resources
VPC Lambda with private subnets prevents unexpected internet connectivity.
Concurrent execution limits set
Prevent runaway Lambda costs from abuse or DDoS. Set reserved concurrency.
Dead letter queue configured
Failed invocations captured for investigation — important for security event reconstruction.
Code signing enforced
Lambda code signing prevents deployment of unsigned or tampered packages.
GuardDuty Lambda Protection enabled
Runtime threat detection inside Lambda execution environment.