A few days ago, I made a poll on Twitter to see what people think is the worst setting for the XSS filter/auditor. The results are very surprising:

In short, the worst goes to X-XSS-Protection: 0, followed by X-XSS-Protection: 1; mode=block, and finally X-XSS-Protection: 1 being the not worst. In my opinion (and probably some others'), the results are surprising because we would expect them to be the other way around (i.e. X-XSS-Protection: 1 should be the worst). In this short blog post I am going to discuss the concerns and hopefully make people realize what the security implications are.

Brief Introduction

In Internet Explorer 8, a defense against XSS attacks was introduced. This browser built-in feature known as XSS filter aims to mitigate reflected XSS. Webkit later had its own version called XSS auditor for Chrome and Safari. The idea is simple: if a malicious input is being reflected in the document, the reflected part will either be removed or the whole document will not be rendered at all depending on the setting.

Regarding setting, websites can explicitly include the X-XSS-Protection header to tell browsers how the filter/auditor should operate. Thare are 3 possible options:

  • 0 (disable XSS filter/auditor)
  • 1 (remove unsafe parts; this is the default setting if no X-XSS-Protection header is present)
  • 1; mode=block (do not render the document if XSS is found)

The Culprits

Let's talk about the problems of leaving the setting default. The first problem is that it widens the attack surface. By abusing false-positives, attackers can selectively disable innocent scripts on the page.

XSS auditor in action

In the above example, the jQuery library is removed despite it should not. This is because auditor cannot distinguish whether a script is injected by attackers or intended for the page. You can imagine if it is a security library, attackers can simply fake an injection and have it removed. In fact, similiar tricks have been used against JS based frame-buster.

The second problem is that it introduces new vulnerabilities. In 2009, a UXSS was found in IE due to a flaw in XSS filter. In essence, attackers can turn harmless markups into harmful ones because the filter incorrectly replaced characters in critical positions and thus damanged the original document structure. Through a crafted payload, it was possible to provide escaping for attribute context. Recently, even more UXSSes were found exploiting XSS filter with the very same root cause in other contexts.

The third problem is that XSS filter/auditor bypasses are inevitable. You may take a look at this, this, this and this. Learning from history, we know that no matter how hard the filter/auditor is improved, there are always potential bypasses. Besides, auditor also has its own limitations in certain contexts. The Chromium team actually makes it clear that XSS auditor is only a defense-in-depth mechanism and it can't catch all possible XSS variants.

So now there should be no argue that XSS filter/auditor is prone to bypasses, and its partial removal approach is problematic. Would the "all or nothing" approach (a.k.a. X-XSS-Protection: 1; mode=block which is the most recommended and common) be savior?

At first glance, it seems to resolve the problem of abused false-positives and elminate the possibility of introducing vulnerabilities in the script removal process, while still provides minimal defense. It surely prevents attackers from abusing false-positives, but it continues to introduce new vulnerabilities unfortunately. The most significant one was a referer leakage bug that led to account hijacking in Facebook. This is also the reason why Facebook decided to disable XSS filter/auditor (i.e. X-XSS-Protection: 0). Other relevant bugs include this and this. Comparing to the above, the number of bugs is relatively much smaller though.

So, Best Option?

To sum up, it really depends on the situation when choosing between X-XSS-Protection: 0 and X-XSS-Protection: 1; mode=block. If you think your application is XSS-free or cannot afford an unusual filter/auditor bug, then go for the former. Otherwise, it's perfectly fine to use the latter in general. The rule of thumb is you never leave it default.