How Google Gaslit Me

This research stemmed from finding myself in an interesting pickle after a client engagement, where one of the topics of discussion was persistence in the context of a browsing session. During this discussion I realised that I did not actually understand what persistence meant when we try and apply it to, for example, the likes of Cross-Site Scripting (XSS).

This prompted me to simply Google “persistent XSS” to see if good ol’ trusty Google had any answers for me; to my surprise I received a bunch of results pointing me in the direction of stored XSS write ups…

This had me quite worried, because simply by applying our understanding of the life cycle of XSS payloads in the Document Object Model (DOM), it meant that stored XSS could not be the answer to the question pertaining to “Persistent XSS”. So I knew something was wrong and I had the sneaking suspicion that Google was trying to gaslight me, and the countless other security professionals that have attempted to venture down this path.

This is where my skepticism had peaked, which was a good thing, I strongly believe that a majority of innovation stems from an initial healthy dose of skepticism. I decided to dive head first into the rabbit hole in an attempt to understand what it meant to achieve true persistence, in a browsing session, utilising XSS.

In this first part of this two-part blog post series, we will break down what persistence means, what our goals are, and why we would want to achieve persistence. We will take a look at the blockers faced, the controls that mitigate our attempts at achieving persistence, as well as the somewhat archaic techniques used in attempts to achieve our goals.

The Rabbit Hole and its Associated Guard Rails

As with any venture into the unknown abyss of some seemingly arbitrary security topic, we need to ensure that we don’t get sucked up and derailed whilst in the rabbit hole. What better way to achieve this than to have a set of high-level research goals.

Specifically for this research it took the form of the following questions:

  • What do we already know about this concept?
  • How far can we push this concept?
  • How can we keep it all practical enough for anyone else to try out?

With these guard rails in-place, I felt comfortable to venture down this rabbit hole without fear of getting lost, while also ensuring that for those who wanted to pick up the research where I left off, it would not be difficult to do so.

Can I Have Some Snake-Oil With That?

Jumping right into the meat of the topic; is stored XSS a valid form of persistence?

Well… it depends. To answer this question we need to break down and understand what persistence means within a web browsing context. Taking a look at the logical flow and structure of XSS we can break its life cycle into two main categories.

Delivery vs Payload

The logical life cycle of an XSS payload consists of two distinct phases, the delivery of your XSS payload, and then the purpose of the payload itself. If we are attributing persistence to the delivery phase then YES! Stored XSS is a form of persistence in that regard, however, for the duration of the research, focus was placed on the payload itself, with a very simple goal in mind; achieving some level of continuous control during a user’s journey on a web application.

Maximum Impact

To understand the goal we want to achieve by implementing persistence within the XSS payload itself, we can explore a common scenario:

You discover that the home page of a banking application is vulnerable to XSS. Great! You found XSS – but digging deeper – if all of the expected security controls are in-place you will find that achieving some solid impact with this would be quite tricky, and would most likely rely on some sort of phishing attack.

If only there was a way to utilise this seemingly low-impact instance of XSS in a way that expands the scope of our potential attack… What if we could stay embedded throughout the victim’s authentication journey? Or even until the victim lands on their main bank account page?

This scenario lays out the goal of what we are striving for. How can we maintain control or some level of influence over a victim’s browsing session with our payload?

Blockers

As with every goal, there will always be things that stand in your way; blockers that you have to overcome. For us to understand what our blockers are we need to first refresh our understanding of XSS.

For most readers the idea of XSS is not new, however, sometimes we tend to forget just how much control we really have over a victim’s browsing session once we have the ability to execute malicious JavaScript (JS) in their browsing context. That level of control should ideally let us do as our heart desires. HOWEVER, it’s not that easy. Some browser concepts and user behaviour gets in the way of achieving our goal.

The two main concepts we will dive into are:

  • Navigation — which can be defined – for the purposes of our research – as any action that results in the re-construction of the DOM. For example, clicking a button to go to the profile page on a forum. What we don’t see happening here is the browser will send a request to the associated profile page’s endpoint, and retrieve the markup for the page in the response body. After this is done, the browser will de-construct the DOM and re-construct it with the new content marking the end of our navigation to the profile page.
  • User interactions — which includes any action where the user might cause the DOM to be de-constructed; for instance, users manually reloading the page, or re-opening the browser and manually navigating to a URL on the target web application.

Both of these concepts have something in common, that being the re-construction of the DOM. If we intend to extend the life cyle of our embedded payload throughout a victim’s browsing journey, then the re-construction of the DOM becomes a problem. This will most likely cause our embedded payload to be removed along with the old content housed in the DOM.

With a clear goal set, and our blockers identified, we are just about ready to kick off into the juicy parts of this research. However, we need to first take a look at the concepts and techniques we will be using as our building blocks.

The Tools In Our Arsenal

Some wonderful techniques and tools have been created throughout the years that attempted to achieve some loose version of persistence. Other pieces of functionality have also been implemented in modern browsers that we can abuse and leverage to help us in our quest. All it really takes to find these tools and techniques is simply going to page 2 of the Google search results for “persistent XSS”.

And once you do, the flood gates open to a whole new world of techniques and tools that start to broaden the scope of our quest. Lets take a look at some examples of these, and examine their potential pitfalls and benefits for the purposes of our quest.

iFrame Trap is a Trap

A blog post written by Drew Kirkpatrick at TrustedSec delves into iFrame traps and how we can utilise them as attackers to monitor user behaviour and interactions with what they think is a legitimate application.

While not a squeaky clean method for persistence, it does border on the idea. Imagine you are exfiltrating a large amount of data or require your victim to stay active on an infected page while your XSS payload executes… This is where user engagement becomes critical, we require the user to stay on the infected page without noticing that anything out of the ordinary is happening.

To maintain this user engagement, we can utilise iFrame traps. With this approach our XSS payload will initiate the exfiltration, afterwich it will dynamically inject an iFrame with its source document pointing to the target web application.

From the user’s perspective, they are simply interacting with the web application as normal; however, what they don’t know is that they are actually interacting with the iFrame our XSS payload injected, which is sitting ontop of the legitimate web application. While the user is interacting with this iFrame, our XSS payload can continue to exfiltrate without interuption from the user (eg. user clicking links killing our exfiltration).

A simple code snippet is shown below, that will spawn an iFrame pointing to the current origin web application leaving our victim oblivious to any other attacks are occuring in the background:

document.body.insertAdjacentHTML(‘beforeend’,'<iframe src=”‘+location.href+'”>’);
…some malicious actions…

The URL you say? Well luckily the browser’s JavaScript API allows us to manually change the URL without performing navigation, further allowing us to convince the victim that everything is in order. Allowing us to continue on our way to exfiltrate and monitor the victims interactions.

Delving further into the technical specifications of this would be stealing Drew’s shine so I highly recommend reading the original post. However, with this said, there are some draw backs…

iFrames are hard to get right, as a starter, and any mistake in your deployment of the iFrame will allude the victim into believing that something is wrong, which blows our cover. This will most likely result in a manual reload or manual navigation, at which point the user’s actions kill our XSS payload. Thus, making it essential for us to minimise any form of UI artifacting that might occur when spawning our iFrame.

However, we still have another problem however…

I Like My BeEF Medium-Rare

The Browser Exploitation Framework, or BeEF for short is a framework that was developed quite some time ago but was really a pioneer in the persistence space, and focused a lot more on the payload and attacks you can achieve by utilising XSS. A great deal of my research was inspired by BeEF, and as we will see later in part 2 of this blog post series, BeEF actually implements a persistence technique that we can utilise to get past the navigation blocker.

However, I do have my quarrels with the tool as great as it may be. The first being how noisy it can get at times; for those of you who have played around with BeEF and hooked a browser will know that when you take a look at the network tab, you are instantly bombarded with an insane amount of HTTP requests, which are required for the Command and Control (C2) communications happening between the hooked browser and the BeEF C2.

We will take a look at why this is an issue and not entirely aligned with our goals in part 2, but for now all we need to know is that BeEF is a great tool that pioneered this area of research, and that we will be drawing inspiration from its persistence technique whilst also aiming for maximum impact.

Attack of the Service Workers

Service worker attacks have become a lot more relevant in recent years, and for very good reason. At a high-level service workers promote some degree of offline usability for web applications that implement them. This is especially handy in countries or areas where internet is not always readily available. However, from our perspective this also makes them a lucrative target.

The technicals of service workers can get intense, however, for the purpose of the content discussed in part 1 and 2 of this blog post series, we need to understand some of said technicals.

Starting off, service workers by nature follow a simple life cycle:

1. Registration/Download
2. Initialisation/Installation
3. Monitoring/Active

To run through a scenario to better understand the life cycle, we will assume that we want to implement service workers for a banking application.

Registration/Download

To register service workers for our banking application, on DOM construction it should execute some JavaScript that pulls a sw.js file containing all the logic for our service workers. A small code snippet would look something like this:

navigator.serviceWorker.register(‘/sw.js’)
.then(reg => console.log(‘SW registered!’, reg))
.catch(err => console.log(‘Boo!’, err));

Take note that we are pointing some JavaScript on our main banking application to that sw.js URL path that contains our service worker logic, this is because service workers run in their own locked down environment, restricted from accessing the DOM on the parent origin web application. This will become important in part 2 when we discuss service worker attacks.

Initialisation/Installation

Once our service worker file has been downloaded and registered, we need to tell it which web application resources we want it to start caching. To do this we specify the resources in the install event of the service worker logic as shown below:

self.addEventListener(‘install’, (event) => {
event.waitUntil(
addResourcesToCache([
‘./’,
‘./bank.html’,
‘./style.css’,
‘./bank.js’,
])
);
});

All that this does, is once the service worker is installed and active, we wait until the browser attempts to fetch any of the specified resources. Once these resources are fetched, it does some background checks in order to determine if the resource is already cached, and if not, the service worker will go ahead and store the response to the initiating request in the cache.

Monitoring/Active

After all the previous steps have finished, in this state the service worker will simply wait and listen for key events that cause the browser to fetch some more resources. Once that event fires off, the service worker will perform some checks (this could be checks to determine whether or not the current version of the resource is the live version) after which, if specified in our install list, the resource will be cached.

I highly recommend diving deeper into service workers as they can be utilised for some very cool attacks.

Now that we have a decent understanding of some of the current tools and techniques, we can jump into the common controls and blockers we will find when attempting to gain persistence or just exploiting XSS in general.

The Controls That Break Our Tools

As with any attacker techniques and tooling, there exists controls that attempt to mitigate or completely prevent the ability for us to utilise them. The list of controls are endless and therefore I have chosen a few that we will be specifically looking at that directly apply to our quest for persistence, namely the security control triad for XSS:

  • Content-Security Policy (CSP)
  • Progressive Web Applications (PWA)
  • Extended Detection and Response (XDR)

The all feared CSP, one of the greatest yet least implemented browser controls out there. The content-security policy header is fairly simple to understand. Its main job is to simply restrict how and where browsers can load resources from. An example of a CSP header that restricts the loading of JavaScript resources from only example.com is shown below:

Content-Security-Policy: script-src https://example.com;

Fairly simple right? This browser control can be specified in responses from an associated web application, and if implemented correctly (subtle foreshadowing) it can prevent XSS altogether, which puts our quest to achieve persistence at risk.

Our quest is not doomed however, a study, completed in 2016 by some Googlers, was performed where 106 billion unique URLs and 1 billion unique hostnames were indexed. It was found that ONLY 3.6% of them implemented a CSP!! And as if things could not get worse out of the 3.6%, only 10% implemented what could be seen as a secure CSP… This is the type of data that makes me as a security consultant feel like I have failed at my job.

On top of this, CSP bypasses go a long way, as Oliver Simonnet discusses in his article Getting Real With XSS, all it takes is one slight oversight in your CSP implementation for it to become obsolete.

So, safe to say that our target web application probably falls into the remaining 103 billion unique instances, and that we won’t have to worry about a securely implemented CSP anytime soon.

Progressive Web Applications

Progressive web applications refer to those built to offer most or all of their functionality offline, through the implementation of service workers. These web applications are fairly common in modern time, and are a prime target for service worker attacks.

Why bring this up under our security controls section? Well, simply to emphasise that service workers can be implemented in a secure manner, that will make it very hard for us to exploit our persistence attacks later on.

Service workers, if implemented properly, will have a limited scope, which in turn limits the scope under which we can achieve our persistence. Additionally, service worker implementations can be customised to perform integrity checking of cached resources which could also pose a problem for us later on.

The important thing to note here is that no attack is immortal. There are probably a bunch of controls out there that if implemented properly aim to mitigate or even prevent our attacks.

XDRs in AppSec?

This one caught me off guard… I’ve always been ignorant to the idea of security monitoring and response tools within a web environment, specifically on the client-side of things. To me this was always a network security problem, and certainly not an XSS problem.

As the scope for common offensive attacks have grown, the scope for defensive tooling has been forced to grow along side that, which starts to pose a problem for us. If we, The innocent application security enthusiasts, have to start bypassing these tools… we definitely have our work cut out for us.

It does however explain why we can’t be satisfied with only one tool like that of BeEF, which will probably get you burnt in an offensive engagement due to the amount of traffic being shoved through the victim’s browser to the BeEF C2. And because of this, in the next part of this blog post series we will see how we can shift our focus a little bit to address this issue and mix in an element of stealth to our quest.

Recappuccino

Within this first part of the 2 part series, we have mainly aimed to get a baseline understanding of where we sit within this domain. Which, if you remember correctly, was one of the goals we had defined for the research.

We now sufficiently understand what concepts and ideas we have to work with here, the different blockers that exist, different tools and techniques, as well as the security controls that go alongside them.

In part 2 of this series we will be diving into the technical rabbit hole I found myself in, where we will layout the game plan for developing our attacks and looking at practical ways we can implement these attacks on our quest for persistence, while still aligning with our very specific but necessary goals.