WP New Vulnerability in Popular Widget Shows Risks of Third-Party Code | Imperva

New Vulnerability in Popular Widget Shows Risks of Third-Party Code

New Vulnerability in Popular Widget Shows Risks of Third-Party Code

The Americans with Disabilities Act (ADA) includes requirements on companies falling within its scope to ensure their websites are accessible to individuals with disabilities. These requirements have created a strong incentive for companies to use third-party solutions in the form of JavaScript libraries to make their websites accessible. However, using third-party solutions comes with its own risks.

Recently, Imperva’s vulnerability research team uncovered a DOM XSS vulnerability in certain versions of a popular accessibility widget by EqualWeb. We want to emphasize that we did not find this vulnerability in the latest version of EqualWeb’s accessibility widget and only found the vulnerability in older versions that are specified at the end of this post.

Successful exploitation of this vulnerability could allow malicious actors to impersonate a user and take over a user’s account, perform any action on behalf of the user and or steal sensitive information such as cookies and session tokens.

According to the EqualWeb website, the EqualWeb accessibility widget is being used by many popular companies and websites such as IronSource, Fiverr, Gett, Bosh, Playtika, Zara, YOT PO and AVIS, as well as financial institutions and public sector and nonprofit entities.

In this post, we will go over the vulnerability, explain how it was detected and what you can do to manage your third-party risks better.

What is DOM-based XSS?

DOM-based XSS vulnerabilities occur when JavaScript uses data from an attacker-controllable source, such as the URL or untrusted message event, and passes it to a method or an API that supports dynamic code execution, such as innerHTML or eval.

DOM-based XSS vulnerabilities are notoriously hard to detect and prevent because they involve the manipulation of the Document Object Model (DOM) rather than the source code of the application. The attacker’s input may not be reflected in the source code, and may only be triggered under certain externally-controllable circumstances, such as post-message events.

Identifying the threat

One of the most common recommendations regarding XSS prevention is to sanitize user input. With that said, some user inputs are harder to spot, which makes them the perfect place to hunt for vulnerabilities.

One of the most overlooked input sources is the browser post-message API.

The post-message API allows for cross-origin communication. This means that a malicious script on one domain can send messages to a script on another domain. These messages can be used to bypass restrictions that the browser has in place to prevent cross-origin communication.

If a website does not properly validate and sanitize messages that are received through the post-message API, it is sometimes possible for an attacker to inject malicious code into the website. This code can then be executed by the website, leading to a range of potential consequences, such as the theft of sensitive information or the execution of unwanted actions on behalf of the user.

Understanding the post-message API

To identify whether a website can be exploited using the post-message API, we need to understand how it works. The post-message API uses the browser’s “message” event. This event is fired when a message is received by a window or a frame. The message event has two properties that are relevant to us:

data: The message that was sent

origin: The origin of the message

To receive messages, the website needs to register an event listener for the message event, which looks something like this:

window.addEventListener('message', function(event) {
document.body.innerHTML = event.data;
});

Once the site registered the event listener, it will start receiving messages. These messages can be sent by other scripts on the same domain or by scripts on other domains. If we want to send a message, we can use the postMessage function. This function takes two arguments:

data: The message that will be sent

targetOrigin: The origin of the window or frame that will receive the message

The following code demonstrates how to send a message:

var target = window.open(“https://example.com”);
target.postMessage(“Hello”, “https://example.com”);

This message will result in the word “Hello” being written to the screen.

However, as you probably noticed, our event listener has 2 main security issues.

  1. We do not validate the message origin, which means any website can send messages to our event listener which could lead to some unintended consequences.
  2. We write unsanitized input to the website DOM by setting the document.body.innerHTML property.

Exploring “message” event handlers on any site

A quick way to find “message” event listeners on any website is to use the “getEventListeners” method from the devtools console; this method returns event listeners of the given object.

We can simply give it the window object, and look for “message” event listeners like so:

getEventListeners(window)
.message.map(e => e.listener)
.map(console.log);

The code above will go over each event listener and print it to the console, you can now click on each listener from the console to jump to the method definition.

The vulnerability

The EqualWeb widget registered a few “message” event listeners, none of which checks if a message was sent from an allowed source. This means any cross-origin site can send messages to those listeners.

While reading each message event listener, the following code was discovered.

window.addEventListener("message", function(i) {
       try {
           const n = e.a11y.GetMsgData(i.data);
           let s = document.querySelectorAll(`iframe[src]:not([src^="javascript"])`);
           e.isIframe && !s || !n || "isNagichOnTop" !== n.INDmessage || t(s, "isNagichOnTop"),
           "getIndModes" === n.INDmessage && t(s, e.mode),
           (e.a11y.validMethods[n.optName] &&
               e.a11y.validMethods[n.optName] == n.method ||
               "INDactivate" === n.INDmessage ||
               "INDactivate" === n.command) && e.LoadData((function() {
                   var t = e[n.method];
                   "function" == typeof t && t(n.data ? n : n.optName)
               }
           ))
       } catch (e) {
           this.INDLog(e, "err")
       }
}

As you can see we have a lot of elaborate checks before reaching what seems to be an interesting piece of code. We started by checking if exploitation is even possible under the best-case scenario. So for now, we are ignoring all the other checks and just focusing on the highlighted section.

The variable “n” holds our message payload, and as you can see, it is used to access a property of the variable “e”, the code then checks that the property is a function and execute it with “n” or “n.optName” as the first argument which is also under our control.

This looks promising, but it all comes down to what methods are there in the “e” variable. Let’s take a look, we wrote this short script to only show the methods in the “e” variable that receives at least one argument.

for (let key in e){
   if (typeof e[key] !== "function"){ continue; }
   if (e[key].toString().indexOf('function()') === 0){ continue; }
   console.log(key,e[key]);
}

After doing that we had 83 methods to review, after going through about 20 we found the one. The getBlock method.

e.getBlock = function(e) {
   let t = $IND(e).parents(".INDblock");
   return $IND(t[t.length - 1]).data("INDblock")
}

This method takes one argument “e” and passes it to $IND which turns out to be  jQuery, which is very helpful since jQuery selector evaluates HTML tags automatically leading to XSS.

Now that we know exploitation is possible, let’s check if we can reach the vulnerable code.

(e.a11y.validMethods[n.optName] &&
               e.a11y.validMethods[n.optName] == n.method ||
               "INDactivate" === n.INDmessage ||
               "INDactivate" === n.command)

The conditions above have to evaluate to true for the vulnerable code to be reached. At first glance it seems n.optName is checked against an allow list, optName is required for our exploit to work. However, when looking closer, the condition itself seems to ignore all of that  if n.INDmessage or n.command is equal to “INDactivate”.

Let’s test it. We pasted the following code in the devtools console, where a vulnerable version of the EqualWeb Javascript code is running.

postMessage(JSON.stringify({"command": "INDactivate", "method": "getBlock", "optName": "<img src='ftp:' onerror=alert(1) >"}))

A few seconds later an alert message appears. To make sure the attack is feasible, w We then confirm messages can be sent from a different domain.

Third-party risk management

As reflected above, companies should be aware of the risks associated with using third-party solutions.

Third-party risk management is the process of identifying, assessing, and mitigating risks associated with using third-party products and services.

As part of third-party risk management, companies should:
– Identify all the third-party products and services they are using
– Assess the risks associated with each third-party product and service
– Mitigate the risks by implementing security controls
– Continuously monitor the third-party products and services for risks

Imperva’s Client-Side Protection can help companies mitigate the risks associated with using third-party products and services by continuously monitoring all JavaScript services and only allowing pre-approved services to execute. This means that any new JavaScript services or changes are blocked until authorized, and if any JavaScript code is poisoned/exploited and attempts to send data elsewhere, your security team will be the first to know.

Just-in-time widgets

Almost all third-party libraries/widgets execute by default for all visitors; in some cases, this is necessary, for example when collecting analytics. However, it’s not clear why widgets for things like accessibility or chat have to be loaded for all visitors.

In general, most of your visitors would not use the chat/accessibility widgets while using the site. With a few lines of code, any developer can make it so that only once the user interacts with the floating chat/accessibility widget the library code will be executed.

It may be a bit slower for the site visitors who choose to use the widget, but from a security point of view, your site attack surface is greatly reduced. If we take the EqualWeb case as an example, sites that utilize this approach would render this attack almost useless since users would have to use the widget before the vulnerability could be exploited.

We recommend that when possible, providers should implement this kind of architecture by default.

IOCs

Vulnerable EqualWeb Accessibility widget versions:

  • 2.0.0
  • 2.0.1
  • 2.0.2
  • 2.0.3
  • 2.0.4
  • 2.1.10
  • 3.0.0
  • 3.0.1
  • 3.0.2
  • 4.0.0
  • 4.0.1

Please note, while it is possible other versions of the widget include the DOM XSS vulnerability, those versions listed above were the only ones we confirmed to be vulnerable. Versions above 4.0.1 and the latest version seem to be unaffected.

Hostnames:

  • aacdn.nagich.com
  • js.nagich.co.il
  • cdn.equalweb.com
  • cdn.uirix.com