July 29, 2026
TryHackMe Complimentary Write-up: From Guest Credentials to Full DynamoDB Access
Complimentary — TryHackMe Write-up

By Adarsh Kumar
3 min read
Complimentary — TryHackMe Write-up
Category: Cloud Security Difficulty: Easy Platform: TryHackMe
Introduction
The Complimentary room demonstrates a common cloud security misconfiguration involving Amazon Cognito Identity Pools, AWS Security Token Service (STS), IAM Roles, and Amazon DynamoDB.
The application advertises a frictionless experience — no login, no registration, and immediate access to personalized wellness data. Behind the scenes, however, the application silently issues temporary AWS credentials to every visitor. While this is a perfectly valid AWS architecture, the security of the entire application depends on how those credentials are authorized.
The objective of this room is to understand how guest users receive AWS credentials and determine whether those credentials grant access beyond what the application intends.
Initial Analysis
Opening the application immediately reveals that there is no authentication process.
Instead, the client-side JavaScript contains the following configuration:
const IDENTITY_POOL_ID = "us-east-1:836c0949-292d-485b-b532-52d5ca7bb688";
const AWS_REGION = "us-east-1";
const TABLE_NAME = "complimentary-GuestWellnessProfiles";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 initializes AWS credentials using:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IDENTITY_POOL_ID
});AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IDENTITY_POOL_ID
});This immediately suggests that the application is using an Amazon Cognito Identity Pool.
Unlike Cognito User Pools, Identity Pools do not necessarily require users to authenticate. They can issue temporary AWS credentials even to unauthenticated (guest) users.
Understanding the Authentication Flow
The architecture works as follows:
Browser
│
▼
Amazon Cognito Identity Pool
│
▼
AWS STS
│
▼
Temporary AWS Credentials
│
▼
Amazon DynamoDBBrowser
│
▼
Amazon Cognito Identity Pool
│
▼
AWS STS
│
▼
Temporary AWS Credentials
│
▼
Amazon DynamoDBWhen the page loads:
The browser contacts the Cognito Identity Pool. Cognito requests temporary credentials from AWS STS.
STS returns:
- Access Key
- Secret Access Key
- Session Token
These credentials are then used directly from the browser to access DynamoDB.
Inspecting the JavaScript
The application retrieves data using:
dynamodb.getItem({
TableName: TABLE_NAME,
Key: {
guest_id: {
S: guestId()
}
}
});dynamodb.getItem({
TableName: TABLE_NAME,
Key: {
guest_id: {
S: guestId()
}
}
});The interesting observation here is that the client only performs a GetItem operation.
The guest ID is randomly generated and stored inside local storage:
guest-xxxxxxxxguest-xxxxxxxxAt first glance, this appears to limit users to their own record.
However, client-side JavaScript only describes how the application chooses to use AWS.
It does not define what the IAM role is actually allowed to do.
Obtaining Temporary Credentials
Using the browser console, the temporary credentials become visible.
The object contained:
- Access Key ID
- Secret Access Key
- Session Token
- Identity ID
- Expiration Time
An important observation is that the Access Key started with:
ASIA...ASIA...AWS temporary STS credentials commonly use the ASIA prefix, indicating that these are temporary session credentials rather than permanent IAM user credentials.
Verifying the Identity
After configuring the AWS CLI with the temporary credentials, the identity can be verified.
┌──(itswiz㉿TheWiz)-[~]
└─$ aws sts get-caller-identity --profile cognito┌──(itswiz㉿TheWiz)-[~]
└─$ aws sts get-caller-identity --profile cognitoThe response:
{
"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 confirms several important facts:
- The browser is not using an IAM user.
- Cognito issued temporary credentials.
AWS STS assumed the role:
complimentary-cognito-unauth-role
This role defines every permission available to guest users.
Investigating the IAM Permissions
The application only performs:
dynamodb:GetItemdynamodb:GetItemThe next logical question becomes:
Does the IAM role allow more than GetItem?
A common DynamoDB operation capable of retrieving every record is:
ScanScanTesting this permission:
┌──(itswiz㉿TheWiz)-[~]
└─$ aws dynamodb scan \
--table-name complimentary-GuestWellnessProfiles \
--region us-east-1 \
--profile cognito┌──(itswiz㉿TheWiz)-[~]
└─$ aws dynamodb scan \
--table-name complimentary-GuestWellnessProfiles \
--region us-east-1 \
--profile cognitoInstead of returning an authorization error, DynamoDB returned the entire contents of the table.
Discovering the Misconfiguration
The Scan response exposed multiple guest profiles containing:
- Names
- Email addresses
- Phone numbers
- GPS locations
- Internal notes
- Password fields (within the CTF scenario)
One particular record contained the flag:
THM{XXX_XXXX_XXXX!}THM{XXX_XXXX_XXXX!}Why Did This Happen?
The vulnerability is not in DynamoDB.
The vulnerability is not in Cognito.
The issue lies within the IAM permissions attached to the guest role.
The application developer intended guest users to access only their own record.
However, the IAM role granted broader permissions than necessary.
Instead of allowing only:
dynamodb:GetItemdynamodb:GetItemfor an individual item, the role also allowed:
dynamodb:Scandynamodb:ScanSince IAM is the service responsible for authorization — not the JavaScript application — the browser was free to invoke any permitted DynamoDB API directly.
This demonstrates an important cloud security principle:
Client-side code cannot enforce authorization. Only IAM policies can.
Security Impact
If this were a production environment, an attacker could retrieve every customer record without authentication.
Potentially exposed information could include:
- Personally identifiable information (PII)
- Internal application notes
- Contact information
- Geographic locations
- Other sensitive business data
A single overly permissive IAM role effectively bypasses the application's intended access controls.
How Could This Be Prevented?
Several best practices would prevent this issue:
- Apply the Principle of Least Privilege to the guest IAM role.
- Avoid granting
dynamodb:Scanto unauthenticated identities. - Restrict access to individual records using IAM policy conditions.
- Place DynamoDB behind an authenticated API (API Gateway + Lambda) rather than allowing direct browser access.
- Validate user authorization on the server instead of trusting client-side logic.
Key Takeaways
This room is an excellent introduction to AWS cloud security because it highlights the interaction between multiple AWS services:
- Amazon Cognito Identity Pools for guest identities
- AWS STS for temporary credentials
- IAM Roles for authorization
- Amazon DynamoDB for data storage
The most important lesson is that authentication and authorization are separate concepts. Cognito successfully authenticated a guest identity and issued temporary credentials, but the attached IAM role authorized far more access than intended.
Even though the application only requested a single DynamoDB item, the overly permissive IAM policy allowed any holder of those temporary credentials to perform a table-wide scan and retrieve every guest profile.
Understanding this distinction between what an application does and what IAM allows is fundamental to securing AWS applications.
Flag
THM{XXXX_XXX_XXXX_XXXX!}THM{XXXX_XXX_XXXX_XXXX!}