Bypassing Anti-incognito Detection in Google Chrome

The browser’s incognito window can help users avoid cookie theft and browsing behavior tracking. Some of the content in this article may be similar or different from other browsers in the series, but I’m focusing on Chromium-based browsers, more specifically Google’s Chrome.

Detect invisible Windows in browsers prior to Chrome 74

Before Chrome 74, there was a bug that many websites exploited to detect if a user was visiting a site using Chrome’s incognito mode. Web sites simply try using the FileSystem API, which is used to store temporary or persistent files. The API is disabled in incognito mode but exists in non-incognito mode, creating a difference that can be used to detect if a user is browsing a website in incognito mode.

A random Google search for traceless Windows yields a number of results, one of which is the Stackoverflow question, and the accepted answer is:

var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if(! fs) {console.log("check failed?");
} else {
    fs(window.TEMPORARY,
        100.console.log.bind(console."not in incognito mode"),
        console.log.bind(console."incognito mode"));
}
Copy the code

Google has introduced a new option in Chrome 74 (accessed via the # enable-Filesystemin-incognito flag) that blocks this detection. Their solution was to create a virtual file system using RAM in stealth mode. This protection can mask the above detection methods and is enabled by default in subsequent stable releases.

How do I detect incognito Windows in Chrome 74 and later?

It turns out that the above protection is inadequate, and it is still possible to detect cloaking patterns that would render the current protection ineffective. Recently, I took a different approach to tinkering with the Quota Management API so that stealth mode can be detected even with this protection enabled. This API manages quotas for temporary and persistent storage allocated to applications and websites on browsers. Using the following code snippet from Jeff Posnick’s article, you can query the quota for temporary storage:

if ('storage' in navigator && 'estimate' in navigator.storage) {
  navigator.storage.estimate().then(({usage, quota}) = > {
    console.log(`Using ${usage} out of ${quota} bytes.`);
  });
}
Copy the code

There are two types of storage available for websites/applications, temporary storage and persistent storage. Temporary storage can be used without requesting any quota and is shared by all websites running on the browser.

I gleaned some interesting ideas about temporary storage and its quotas through reading Chromium source code, articles, and bug reports :(Ref1, Ref2, Ref3)

  • For all applications/websites, the default quota for temporary storage is 50% of the available disk space, which acts as a shared pool for all applications/websites

  • Applications/websites can query their quotas by calling the Quota API’s queryUsageAndQuota() method without requiring any permissions

  • The invisible window quota is a small portion (10%) of the device’s memory, capped at 120MB

  • The quota for non-invisible Windows is only a small part of the device’s storage

The following table lists the minimum available temporary storage quotas for devices of different disk sizes, calculated based on when the browser tries to always be idle on the device.

As shown in the figure above, the key difference between the temporary storage quota in incognito mode and non-incognito mode is that in incognito mode, the temporary storage quota is fixed at 120MB, while the non-incognito window is not. As shown in the preceding table, in non-stealth mode, the temporary storage quota is less than 120MB only when the device storage capacity is less than 2.4GB. However, for all practical purposes, it is safe to assume that most devices currently in use have more than 2.4GB of storage, meaning that the temporary storage quota must be greater than 120MB in non-stealth mode.

Using this information, I came up with a simple rule for detecting cloaking patterns. If the temporary storage quota is <= 120MB, you can be sure that this is an invisible window.

if ('storage' in navigator && 'estimate' in navigator.storage) {
	const {usage, quota} = await navigator.storage.estimate();
    console.log(`Using ${usage} out of ${quota} bytes.`);

	if(quota < 120000000) {console.log('Incognito')}else {
        console.log('Not Incognito')}}else {
	console.log('Can not detect')}Copy the code

What can you do once you detect that a user is using incognito mode? Comments are welcome…