The lab
here is how portswigger describes it
This lab demonstrates a simple web message vulnerability. To solve this lab, use the exploit server to post a message to the target site that causes the print() function to be called.
The idea#
normally the same origin policy keeps one site's scripts from reaching into another site's page. web messaging is sanctioned exception to that. one window can hand another window a piece of text, even across origins, by calling postMessage on it and the receiving window catches it with a message event listener. it is a perfectly safe feature as long as the receiver does two things, checks who the message came from, and treats the text as data rather than as code.
the bug appears when a page skips both of those. if it listens for any message from anyone and then drops the message straight into the page as html, an attacker who can send it a message controls that html and that is a dom based xss sitting there waiting for a message to arrive.
Step 1 - Read the source and find the listener#
there is no login or search here so recon means reading the page source and in it there is a script that sets up a message listener.

<script>
window.addEventListener('message', function(e) {
document.getElementById('ads').innerHTML = e.data
})
</script>
reading it through. window.addEventListener('message', ...) registers a handler that fires whenever this window receives a web message and the message arrives as the event object e, with the text of the message in e.data. the handler then takes that e.data and assigns it to the innerHTML of the ads element. two things are missing that should not be, it never checks e.origin to see who sent the message, so it will accept one from anybody, and it uses innerHTML, which parses the value as real html rather than printing it as text. so whatever we send as a message becomes live markup on their page. that is our sink.
Step 2 - Post a message from iframe#
we cannot send the message from an unrelated tab, we need a handle on the target's window. the clean way is to load the target inside an iframe on our own page, because then the iframe gives us a reference to its window and we can post a message straight to it. here is the page we host on the exploit server.
<iframe src="https://your-lab-id.web-security-academy.net/" onload="this.contentWindow.postMessage('<img src=1 onerror=print()>','*')"></iframe>
here is why it is built this way. the <iframe> loads the target site, and this.contentWindow is a reference to the window inside that iframe, the target's own window. we call postMessage on it to send our message, and the message we send is the payload <img src=1 onerror=print()>. when the target's listener receives it, it writes that into ads as html, the browser tries to load an image called 1, fails runs the onerror and calls print. the '*' is the second argument to postMessage the target origin and it means send this to whatever origin happens to be loaded there rather than restricting it to a specific one, which is fine for us since we just want it delivered.
store this on the exploit server and deliver it to the victim.

when the victim's browser loads our page, the iframe loads the target and the instant it is ready our onload posts the payload to it, the target's listener writes it into the page as html, and print is called.
with this, the lab is solved!
