MD Notes
postMessage information disclosure leads to stored XSS
Last updated
postMessage information disclosure leads to stored XSS
Last updated
Description: Here's a nice web application to host your notes.
Author: yadhu#2142
We are given a Markdown Editor, where we can save our notes. As usual, an admin bot visits the URLs that we submit.
We're interested in creating an XSS payload, so let's analyse how the application processes our Markdown.
Interestingly, the preview (right side) is an iframe of /demo
.
When clicking "Preview", a message is posted to the iframe. Note that the targetOrigin
parameter is set to http://${document.location.host}/
. This ensures that the message is only sent to the intended receiver.
In /demo
, the message is received and processed. The data is POST-ed to the /api/filter
endpoint, and the sanitized HTML is added to document.body.innerHTML
.
Interestingly, in line 18, the response from the /api/filter
endpoint is also posted to window.parent
, with the targetOrigin
parameter set to *
. This means that any site can create an iframe of http://web.challenge.bi0s.in:5432/demo
and receive the message, regardless of its origin.
Notice that the user's cookies are sent along with the POST request. If the /api/filter
endpoint returns any sensitive, user-specific data based on the user cookies, we would be able to read it! If we analyze the server code for this endpoint, we would know that it indeed returns the hash of the user's token in the response.
Now, we can craft a simple payload that loads /demo
in an iframe, posts a message to trigger the /api/filter
POST request in the context of the admin, and catches the response. We then make a callback to our exploit server, sending the admin's hash obtained from the response.
We receive the admin's hash on our exploit server:
However, the admin's hash is not sufficient to access the flag - we need to have access to the admin's token. This requires a CSRF to /api/flag
, and due to the same-origin policy, we must still cause an XSS on the challenge server.
Now that we have the admin's hash, creating a stored XSS payload is pretty simple. Notice that in the /api/create
handler, the data is not sanitized if the admin's hash is used.
Thus, simply sending a POST request to /api/create
with the admin's hash allows us to create a stored XSS payload.
We can simply craft a CSRF payload that fetches /api/flag
and makes a callback to our exploit server with the page contents. Note that single and double quotes are still escaped, so fromCharCode()
is used to avoid that.
Receiving the /api/flag
contents:
URL-decode the output, and we get the flag: inctf{8d739_csrf_is_fun_3d587ec9}