INVOXES Blog

Yello. I'm Kousha (@INVOXES). I'm a FreeBSD/Arch user and a Web/Android PenTester. I love Reverse Engineering/Malware Analysis. I'll write down my things in this blog. :)

postMessage and Misconfigurations

I’ll discuss the postMessage feature and how it can be vulnerable by wrong way implementation. First I’m going to talk about what is postMessage feature actually, then we are going to code and use it, at the end we will take a look at vulnerabilities.

What is postMessage?

postMessage() is a feature introduced in HTML5 and you can use it in JavaScript. This feature lets you send data between different Window objects (it can be an iframe or window.open()).

Same-Origin Policy (SOP) is a mechanism that blocks the cross-origin requests, It means if we request a resource that is not at the same origin, our request will send but the response will return an error. In a word Origin is:

Origin = Protocol + Domain + Port

SOP Will return an error in response if one of the protocol, domain, or even port changes. postMessage provided a secure way that let you bypass this security mechanism.

A postMessage() syntax is something like:

targetWindow.postMessage(message, targetOrigin, [transfer]);

Data Sender

Now we want to write a code that send a data to an iframe.

┌────────────────────────────────┐
│          domain-a.com          │
│  ┌─────────────────────────┐   │
│  │                         │   │
│  │                         │   │
│  │         <iframe>        │   │
│  │                         │   │
│  │       domain-b.com      │   │
│  └─────────────────────────┘   │
│                                │
│  ┌─────────────────────────┐   │
│  │Write message here...    │   │
│  └─────────────────────────┘   │
│                                │
│        ┌─────────────┐         │
│        │  Send Msg   │         │
│        └─────────────┘         │
│                                │
└────────────────────────────────┘

It’s so much easy. Right?! :)

hxxp://domain-a.com/sender.html:

<html>
 <head></head>
 <body>
  <form id="form1" action="/">
   <input type="text" id="message" placeholder="Write message here...">
   <input type="submit" value="Send Msg">
  </form>
  <iframe id="ifrm" src="http://domain-b.com/receiver.html">
   <script>
    window.onload=function() {
    var ifrmwindow = document.getElementById("ifrm").contentWindow;
    var form       = document.getElementById("form1");
    var msg        = document.getElementById("message").value;
    form.onsubmit = function() {
      ifrmwindow.postMessage(msg, "http://domain-b.com");
      return false;
      }
    }
   </script>
 </body>
</html>

First, we take the Window object of the iframe and the message which we want to send, then we set a function() for onsubmit event of the form, that call postMessage() on the iframe Window object. You can also set * instead of http://domain-b.com. The * means this message can send to anyone, actually you can specify the origin in that, but we set it as * which means it could be anyone (any origin), sooo… Never do that in the production stage. :))

Now we need to code the receiver of postMessage(). I just want to write what receiver got in the page.

hxxp://domain-b.com/receiver.html:

<html>
 <head></head>
 <body>
  <p id="received-message">Nothing got yet!</p>
  <script>
   function displayMessage(event) {
    msg = "Message: " + event.data + "<br>Origin: " + event.origin;
    document.getElementById("received-message").innerHTML = message;
   }
   
   if (window.addEventListener)
    window.addEventListener("message", displayMessage, false);
   else
    window.attachEvent("onmessage", displayMessage);
  </script>
 </body>
</html>

addEventListener() will wait for an event, if it receive an event it will call a function which specified. attachEvent() is like addEventListener() but instead for Internet Explorer and Opera. "message" is the type of event that we waiting for. displayMessage is a function which will be call when we get what we waiting for ("message").

Now we know how a postMessage() work. Let’s move on and explain what vulnerabilities may occur.

postMessage Vulnerabilities

Three vulnerabilities can occur in postMessage.

The first one is obvious and we pointed out before. Now I want to show you how to abuse the second and third misconfigurations.

As we already discussed, receiver wait for an event (based on the type of addEventListener()) then a function will call, but we didn’t said who can send data to receiver?! It has a simple answer, EVERYONE!

So everone can send data to any receiver, It is on receiver own to check the sender origin, If it doesn’t, the receiver “event listener” will accept any event. It means an attacker also can send a malicious data to receiver. A secure way to fix this issue is checking the Origin. It just needs a if condition. Let’s explain by an example.

hxxp://domain-b.com/receiver.html:

<html>
 <head></head>
 <body>
  <p id="received-message">Nothing got yet!</p>
  <script>
   function displayMessage(event) {
    if (evt.origin.startsWith("http://domain-a.com") != true) {
     console.log("Invalid Origin! Do Not try Hacking at home. :)");
    } else {
     msg = "Message: " + event.data + "<br>Origin: " + event.origin;
     document.getElementById("received-message").innerHTML = message;
    }
   }
   
   if (window.addEventListener)
    window.addEventListener("message", displayMessage, false);
   else
    window.attachEvent("onmessage", displayMessage);
  </script>
 </body>
</html>

I think it doesn’t need any explain. It just like the previous receiver, the only difference is an if condition. It checks if origin is started with “hxxp://domain-a.com”, If an attacker try to send a data with a “hxxp://attacker.com”, the receiver will write an error log. BUT, what if an attacker send a data by “hxxp://domain-a.com.attacker.com” domain?! Yeaaa… Bypassed! The receiver will accept data. A secure way to implement is to check complete origin instead of startsWith or endsWith to compare a part of origin.

<html>
 <head></head>
 <body>
  <p id="received-message">Nothing got yet!</p>
  <script>
   function displayMessage(event) {
    if (evt.origin != "http://domain-a.com") {
     console.log("Invalid Origin! Do Not try Hacking at home. :)");
    } else {
     msg = "Message: " + event.data + "<br>Origin: " + event.origin;
     document.getElementById("received-message").innerHTML = message;
    }
   }
   
   if (window.addEventListener)
    window.addEventListener("message", displayMessage, false);
   else
    window.attachEvent("onmessage", displayMessage);
  </script>
 </body>
</html>

Now it is better! :)

Now we get to the last misconfiguration. Consider a simple chatroom is implemented by the postMessage() functionality. There is a server which accept data from anywhere, It shows everyone data (message) that sent just like the first example that we implemented. The receiver shows the message by innerHTML JavaScript function.

Break (DOM-Base XSS)

Cross-Site Scripting (a.k.a XSS) is a vulnerability that occur when user inputs reflect somewhere without sanitizing. It leds attacker to inject a script into the page that JavaScript is the most common one scripting language to exploit this vulnerability. XSS has different types:

Back to the Future (postMessage)

innerHTML is one of the vulnerable Javascript property that leads to DOM-based XSS. As you can see in the first example of receiver.html, the received data (message) are displayed by innerHTML. So what now?! Just send <img src=x onerror=alert(1337)> and now we exploited a XSS vulnerability.