DEV Community

Cover image for XS-Leaks: Is Your Website Exposing Sensitive Data?
Teo Selenius
Teo Selenius

Posted on • Updated on • Originally published at appsecmonkey.com

XS-Leaks: Is Your Website Exposing Sensitive Data?

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.

What are XS-leaks?

XS-Leaks (or Cross-Site Leaks) are a set of browser side-channel attacks. They enable malicious websites to infer data from the users of other web applications.

The Twitter silhouette attack was a superb example.

Same Origin Policy

Before we get started, it's helpful to understand SOP (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 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.

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);
});
Enter fullscreen mode Exit fullscreen mode
The request took 129 ms.
Enter fullscreen mode Exit fullscreen mode

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

{"records": [{"id": 1, ...}]}
Enter fullscreen mode Exit fullscreen mode

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

GET /api/v1/records/search?query=diabetes

{"records": []}
Enter fullscreen mode Exit fullscreen mode

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.

See all of the timing attacks on 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.

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');
Enter fullscreen mode Exit fullscreen mode
[-] GET https://www.example.com/ succeeded.
[+] GET https://www.example.com/this-does-not-exist returned error.
Enter fullscreen mode Exit fullscreen mode

XS-leaks through frame counting

By obtaining a handle to a frame, it is possible to access the frame's 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, which returns the handle.

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);
Enter fullscreen mode Exit fullscreen mode

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

 <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>
Enter fullscreen mode Exit fullscreen mode

Those two are arguably the most important. There are others, such as window.opener and window.parent. See this article for a more comprehensive list.

Read more about frame counting on xsleaks.dev.

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/

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/.

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 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/

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 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. When you set a cookie with SameSite=Lax, browsers will not include it in cross-origin POST requests, which works nicely against CSRF 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.

Set-Cookie: SessionId=123; ...other options... SameSite=Lax
Enter fullscreen mode Exit fullscreen mode

Use Content-Security-Policy and X-Frame-Options to prevent framing

Another beautiful browser feature is the Content Security Policy, or CSP for short. CSP can be incredibly effective against XSS attacks. But in this case, we are interested in blocking framing, and the CSP recipe for that is:

Content-Security-Policy: frame-ancestors 'none';
Enter fullscreen mode Exit fullscreen mode

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.

X-Frame-Options: DENY
Enter fullscreen mode Exit fullscreen mode

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 header in all your responses:

Cache-Control: no-store, max-age=0
Enter fullscreen mode Exit fullscreen mode

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 the cache based on the fetch metadata request headers.

The 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.

Vary: Sec-Fetch-Site
Enter fullscreen mode Exit fullscreen mode

Implement a Cross-Origin Opener Policy

The 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:

Cross-Origin-Opener-Policy: same-origin
Enter fullscreen mode Exit fullscreen mode

It is already fully supported in Chrome, Edge, and Firefox.

Implement a Cross-Origin Resource Policy

The 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. You can set it like so:

Cross-Origin-Resource-Policy: same-origin
Enter fullscreen mode Exit fullscreen mode

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 is to take advantage of the new fetch metadata request headers, 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.

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/, you should be fine.

Get the web security checklist spreadsheet!

Subscribe
☝️ Subscribe to AppSec Monkey's email list, get our best content delivered straight to your inbox, and get our 2021 Web Application Security Checklist Spreadsheet for FREE 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 as well.

Thanks for reading.

Top comments (0)