XS-Leaks: Is Your Website Exposing Sensitive Data?
Published: 2021-02-24 07:07:22


Are you exposing your users to malicious websites? Learn how xs-leaks are used to exfiltrate data from a web application and how to prevent it in 7 steps.

The original and most up-to-date post can be read [here](https://www.appsecmonkey.com/blog/xs-leaks/).

## What are XS-leaks?
XS-Leaks (or Cross-Site Leaks) are a [set of browser side-channel attacks](https://xsleaks.dev/). They enable malicious websites to infer data from the users of other web applications.

The [Twitter silhouette attack](https://blog.twitter.com/engineering/en_us/topics/insights/2018/twitter_silhouette.html) was a superb example.

## Same Origin Policy
Before we get started, it's helpful to understand SOP ([Same Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)), which is the heart and soul of the web browser security model. It is a rule that more or less says: 

1. Two URLs are of the same *origin* if their protocol, port (if specified), and host are the same.
2. A website from *any* origin can freely send `GET`, `POST`, `HEAD`, and `OPTIONS` requests to *any other* origin. Furthermore, the request *will* include the user's cookies (including the session ID) to that origin.
3. While sending requests is possible, a website from one origin cannot directly *read* the responses from another origin.
4. A website can still *consume* resources from those HTTP responses, such as by executing scripts, using fonts/styles, or displaying images. The [JSONP](https://www.w3schools.com/js/js_json_jsonp.asp) hack takes advantage of this fourth rule (don't use JSONP).

For example, this website's origin is `https://www.appsecmonkey.com/` where the `protocol` is `https`. The host is `www.appsecmonkey.com`, and the port is not specified (which is implicit `443` because of the `https` protocol).

Alrighty, that's the gist of it. Let's get to the attacks.


## XS-leaks through timing attacks
Browsers make it easy to time cross-domain requests.

```js
var start = performance.now()

fetch('https://example.com', {
  mode: 'no-cors',
  credentials: 'include'
}).then(() => {
  var time = performance.now() - start;
  console.log("The request took %d ms.", time);
});
```

```
The request took 129 ms.
```

This makes it possible for a malicious website to differentiate between responses. Suppose that there is a search API for patients to find their own records. If the patient has diabetes and searches for "diabetes", the server returns data.

`GET /api/v1/records/search?query=diabetes`
```yaml
{"records": [{"id": 1, ...}]}
```

And if the patient doesn't have diabetes, the API returns an empty JSON.

`GET /api/v1/records/search?query=diabetes`
```yaml
{"records": []}
```

Generally, the former request would take a longer time. An attacker could then create a malicious website that clocks requests to the "diabetes" URL and determines whether or not the user has diabetes.

You can expand the attack and search for a... b... c... d... yes. da.. db... di... yes. This sort of attack is known as [XS-search](https://xsleaks.dev/docs/attacks/xs-search/).

See all of the timing attacks on [xsleaks.dev](https://xsleaks.dev/).


## XS-leaks through error-based attacks
The next side-channel on our list is strategically catching error messages with JavaScript. Suppose that a page returns either `200 OK` or `404 not found`, depending on some sensitive user data.

An attacker could then create a page like the following, which queries the application and determines whether the endpoint returns an error for the browser user or not.

```js
function checkError(url) {
  let script = document.createElement('script');
  script.src = url;
  script.onload = () => console.log(`[+] GET ${url} succeeded.`);
  script.onerror = () => console.log(`[-] GET ${url} returned error.`);
  document.head.appendChild(script);
}

checkError('https://www.example.com/');
checkError('https://www.example.com/this-does-not-exist');
```

```
[-] GET https://www.example.com/ succeeded.
[+] GET https://www.example.com/this-does-not-exist returned error.
```

## XS-leaks through frame counting
By obtaining a handle to a frame, it is possible to access the frame's [window.length](https://developer.mozilla.org/en-US/docs/Web/API/Window/length) property which is used to retrieve the number of frames (IFRAME or FRAME) in the window.

This knowledge can sometimes have security/privacy implications. For example, a website may render a profile page differently with a varying number of frames based on some user data.

There are a couple of ways to obtain a window handle. The first is to call [window.open](https://developer.mozilla.org/en-US/docs/Web/API/Window/open), which returns the handle.

```js
var win = window.open('https://example.com');
console.log("Waiting 3 seconds for page to load...");
setTimeout(() => {
  console.log("%d FRAME/IFRAME elements detected.", win.length);
}, 3000);
```

Another is to frame the target website and get the handle of the frame.

```html
 <iframe name="framecounter" src="https://www.example.com"></iframe>
    <script>
        var win = window.frames.framecounter;

        console.log("Waiting 3 seconds for page to load...");
        setTimeout(() => {
            console.log("%d FRAME/IFRAME elements detected.", win.length);
        }, 3000);
    </script>
```

Those two are arguably the most important. There are others, such as `window.opener` and `window.parent`. See [this article](https://bluepnume.medium.com/every-known-way-to-get-references-to-windows-in-javascript-223778bede2d) for a more comprehensive list.

Read more about frame counting on [xsleaks.dev](https://xsleaks.dev/docs/attacks/frame-counting/).

## XS-leaks through detecting navigations
Knowing whether or not the browser navigated (e.g., redirected) somewhere, it is often possible to infer data about the user. For example, authenticated portions of websites tend to redirect the user to the login page. Unless, of course, the user is logged in already.

Observing navigations gives malicious websites the power to see which websites the browser user is logged in to, which is a huge privacy concern.

There are multiple ways by which malicious websites can detect redirects. These include: 

- Creating a frame and counting how many times `onload` is called.
- Retrieving the `history.length` from a window handle.
- Creating a Content Security Policy (CSP) on the malicious website triggers exceptions when specific URL addresses are requested.

See them all here: [https://xsleaks.dev/docs/attacks/navigations/](https://xsleaks.dev/docs/attacks/navigations/)

# XS-leaks through browser cache
When users visit websites, the resources from those sites are usually cached and stored on the user's disk so they won't have to be downloaded again. This saves bandwidth, lowers server load, and improves user experience.

Unfortunately, the timing- and error-based xsleak variations can take advantage of this and determine whether a user has visited a website before.

The cache timing variation is simple, time the request, and if it's instantaneous, then the resource was cached.

The error-based version is slightly more involved. It's taking advantage of the fact that cached resources are never actually requested from the server. As such, an invalid HTTP request for a cached resource does not raise an exception (because the web server never gets a chance to reject it).

Read about both of them on xsleaks: [https://xsleaks.dev/docs/attacks/cache-probing/](https://xsleaks.dev/docs/attacks/cache-probing/).

# XS-leaks through ID-fields in frames
This one takes advantage of the fact that the [https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event](focus) event gets fired when a frame with an url like `https://www.example.com/#example` jumps to the element `example`.

If there is no `example` on the page, the event doesn't fire.

Read more about this variation on xsleaks: [https://xsleaks.dev/docs/attacks/id-attribute/](https://xsleaks.dev/docs/attacks/id-attribute/)

## XS-leaks through many other things
The variations mentioned thus far should give you the idea, but there are others. You can go to [xsleaks.dev](https://xsleaks.dev) for more attacks and details.


## How to prevent XS-leaks?
You won't be able to completely prevent *all* xsleaks in *all* browsers. The world isn't ready for that yet. But you can be pretty safe, especially for Chrome or Edge, which have all the bleeding edge security features under their belts. Here's how: 

1. Protect your cookies with the SameSite attribute.
2. Use Content-Security-Policy and X-Frame-Options to prevent framing.
3. Consider using Cache-Control to disable caching.
4. Use the fetch metadata headers and the Vary header to prevent cache probes.
5. Implement a Cross-Origin Opening Policy.
6. Implement a Cross-Origin Resource Policy.
7. Implement an isolation policy.

## Protect your cookies with the SameSite attribute
All major browsers support a cool feature called [SameSite cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). When you set a cookie with `SameSite=Lax`, browsers will not include it in cross-origin POST requests, which works nicely against [CSRF](https://www.appsecmonkey.com/blog/csrf-attack-and-prevention/) attacks.

Crucially for our xsleaks use case here, it also blocks GET requests that are not *top-level navigation*, which is to say, that script-tags, fetch-requests, image tags, etc., will not send the cookie anymore.

```yaml
Set-Cookie: SessionId=123; ...other options... SameSite=Lax
```

## Use Content-Security-Policy and X-Frame-Options to prevent framing
Another beautiful browser feature is the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), or CSP for short. CSP can be incredibly effective against [XSS](https://www.appsecmonkey.com/blog/xss-attack-and-prevention/) attacks. But in this case, we are interested in blocking framing, and the CSP recipe for that is: 

```yaml
Content-Security-Policy: frame-ancestors 'none';
```

This CSP policy will prevent all modern browsers from letting any other website frame your application. If you want to support Internet Explorer as well, then also send [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).

```yaml
X-Frame-Options: DENY
```

## Consider using Cache-Control to disable caching
Disabling the cache is not for everyone. I, for example, couldn't possibly do it for this blog. But arguably, the most effective way to prevent caching-related xsleaks vectors is to disable caching for your website altogether. You can do this by returning the following [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header in all your responses: 

```yaml
Cache-Control: no-store, max-age=0
```

## Use the fetch metadata headers and the Vary header to prevent cache probes.
This approach is much more feasible but not supported by all browsers yet. The idea is to [Vary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) the cache based on the [fetch metadata request headers](https://www.w3.org/TR/fetch-metadata/).

The [Sec-Fetch-Site](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site) request header will contain the value `cross-site` if a malicious website attempts to make requests to your application. And because of the `Vary` header, that malicious website will sort-of have a cache of its own. It will not be able to deduce what the browser user has cached on the website.

```yaml
Vary: Sec-Fetch-Site
```


## Implement a Cross-Origin Opener Policy

The [Cross-Origin-Opener-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) is an HTTP response header that restricts malicious websites from obtaining a window handle to your website. You can set it like so: 

```yaml
Cross-Origin-Opener-Policy: same-origin
```

It is [already fully supported](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy#browser_compatibility) in Chrome, Edge, and Firefox.


## Implement a Cross-Origin Resource Policy
The [Cross-Origin-Resource-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy) is an HTTP response header that restricts malicious websites from reading/embedding/rendering resources from your domain. Read more about it [here](https://resourcepolicy.fyi/). You can set it like so: 

```yaml
Cross-Origin-Resource-Policy: same-origin
```

## Implement an isolation policy
It is possible to block cross-origin requests on the server-side as well. However, this is the bleeding edge, so you will probably have to write your middleware to do it.

One solution [proposed by xsleaks.dev](https://xsleaks.dev/docs/defenses/isolation-policies/strict-isolation/#implementation-with-fetch-metadata) is to take advantage of the new [fetch metadata request headers](https://www.w3.org/TR/fetch-metadata/), and block *any* requests with the `Sec-Fetch-Site` value of `cross-origin`. This radical approach will doubtless improve your security. But it will also affect your UX because hyperlinks to your application will cease to work.

They also propose more targeted isolation policies that help against some attacks but don't necessarily affect usability so much. Read more about them [here](https://xsleaks.dev/docs/defenses/isolation-policies/).

## Conclusion
There are quite a few XS-leaks, and browser vendors are coming up with tools to beat them as we speak.

Preventing all of them is not easy, if even possible. But by following the guidelines in this article and on [https://xsleaks.dev/](https://xsleaks.dev/), you should be fine.

## Get the web security checklist spreadsheet!
[![Subscribe](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iqd1ef4a5a0icyvvk65.jpg)](https://eepurl.com/hqAGt5)
 ☝️ [Subscribe](https://eepurl.com/hqAGt5) to AppSec Monkey's email list, get our best content delivered straight to your inbox, and <b>get our 2021 Web Application Security Checklist Spreadsheet for FREE</b> as a welcome gift!

## Don't stop here
If you like this article, check out the other application security guides we have on [AppSec Monkey](https://www.appsecmonkey.com/) as well.

Thanks for reading.