Bypassing Chrome’s Anti-XSS filter

Its been a while since my last post so I decided to make it worthwhile :). I was recently checking a friend’s site for the classic Web application vulnerabilities, when I found a reflected XSS attack. While I was investigating the bug, I noticed that while the bug worked on Mozilla’s Firefox, it didn’t work on Google’s Chrome. As it turns out, Chrome uses an Anti-XSS filter, based on static analysis, which attempts to detect XSS. If it detects such an attempt, it filters out the injected code, and effectively stops the on-going attack.

In order to demonstrate this, I made a vulnerable page at http://securitee.tk/files/chrome_xss.php. This page simply reads two GET parameters, namely a and b, which it then prints out in the resulting page.

To show that injection is possible, I start by injecting some HTML which is indeed rendered as part of the HTML page.

If you pay attention to the part that I have placed in the red box on the right of the screen, you will notice that Chrome detected my injected JavaScript and filtered out the alert function, leaving me with an empty script. The next thing I tried, was to omit the closing script tag and see how the browser would react to that:

http://securitee.tk/files/chrome_xss.php?
a=<script>alert(1);&b=bar

Ommiting the closing script tag

In this case, Chrome didn’t remove my script (actually it tried to finish it by including a closing script tag of its own, right before the end of the body tag) but it didn’t work since all the normal text and HTML is now in the script environment. Given the fact that HTML is not valid JavaScript, the interpreter fails and still we don’t get the alert box. All that needs to be done is to somehow make the JavaScript engine ignore the HTML and text between our two controlled variables. This can be easily achieved by using JavaScript multi-line comment delimiters.

And indeed, it worked!!! The multi-line comments mean nothing to the HTML but mean the world when they are placed in a script environment In summary, all you need to bypass the XSS filter is to have at least two variables under your control, and break up your injected script, with the help of multi-line comments, to use both.

Till next time
Nick Nikiforakis

P.S. I have already told the Chrome folks about this, but their answer was that their filter is not meant to protect against this attack… I don’t know why… you can ask them

Update – January 19th 2012

Recently I noticed that the attack explained above stopped working. The developers of Chrome still say that if you control two variables you can bypass the Anti-XSS but at the same time they agree that the bypass that I had originally demonstrated is no longer working. So here is a bypass, for their filtering of my original bypass (tested in Linux Chrome 17.0.963.38 beta)

Now if you look at what’s happening here, all I am doing is adding a double-slash comment right at the end of the first controlled variable. This shouldn’t make any difference since we are already in comment-mode but apparently it does. If I would have to guess, I’d say that they have really hard-coded the multiline comment rule in their filters and any small deviation will still do the trick.

Update – October 25th 2012

Today one of the Master students who I supervise here at KU Leuven told me that my attack no longer works. After verifying that he was right, I tried out a modified bypass that I had discovered some months ago but “failed” to disclose. Works like a charm
(tested in Linux Chrome 23.0.1271.40 beta)

Instead of using comments to get rid of the intermediate HTML, I just make the whole thing a string and give it to the JavaScript function void, who couldn’t care less :). Using this, the script environment is ready to accept your arbitrary code, which in our case is the usual alert(1).

The reason we can’t mitigate this vulnerability is that you’re able to inject content into the page in two locations. We’re working to reduce the false negative rate of the filter. You can follow our work by looking at the dependency tree of this bug:

Thanks for your response. Would any issues arise if you would just concatenate all GET variables from the URL and search for JavaScript in there? It seems to me that you would keep on detecting the attacks you do today, plus the one I mention in this post. Is there a false-positive somewhere in there that I am not thinking of?

concatenating alone would not be sufficient enough I think. The HTML could include the parameters in another sequence than the sequence of the GET parameters. So you somehow need take the internal ordering into account. Which is very difficult if you do not know the internals of a web page.

Another way to confuse the detection mechanism would be to simply introduce bogus GET parameters to prevent the mechanism from finding valid JavaScript.

Honestly I don’t think the problem is with Chrome’s anti XSS filter as this is supposed to just be a security blanket for stupid developers. At the end of the day Chrome is just doing what it can to help with the most common XSS styled attacks to protect its users and it falls on the hands of the developer of the vulnerable web app to ensure their code is not vulnerable in the first place.

If Chrome can fix this bypass that’s great, if not, hopefully it keeps pressure on web application developers to ensure their code is secure instead of thinking, “It’s cool, Chrome will stop the attack”.

I agree to the idea that a developer should be responsible for vulnerabilities on a website. We cannot depend on browsers to protect us from web attacks since there still are a million ways to bypass filters. If the developer can code securely, all bypass techniques are useless.

Thanks for your comment. The problem with the textarea is that it is HTML and not JavaScript code, thus it won’t work. You need a way to make the JavaScript engine ignore the various tags between your two parameters, while staying in the JavaScript environment – which is where multi-line JS comments come in hand.

So … there is another attack possibility, rather than having to have 2 *separate* parameters to inject into a page – it’s conceivable to have the same parameter echo twice, and have an attack crafted that would effective still do the same thing?

As you may or may not know, I am a PhD student in Computer Science so, with your permission, I will approach your comment, scientifically.

1) You say “there are ton of filter bypasses”. Where are the references towards this “ton” of resources? Should I take your word for it or can it be shown to me, objectively, that you are right? Again, as you may or may not know, my original blog post (without the two updates) was voted one of the top 10 Web hacking techniques of 2011. Strange that I would be voted for such a thing if there was a “ton” of bypasses and the filter was “known, not to protect”.

2) You say that the filter is “not even maintained”. Again, if you would have taken the time to read through the whole blog post, you would have found out that, twice over the last year, I published a way around the filter and twice the bypass was fixed (this last one is my third one). Again, what a strange behaviour for a filter “not even maintained”.

3) Your writing style is pseudo-friendly (even though we are not friends) and marginally derogatory, which is something that I do not enjoy, especially given the lack of any real content in your comment.

In short, your comment is rejected. If you think you are right, please revise and resubmit.

Although your latest “hack” still works in the latest version of chrome, it failed on Internet Explorer 9.0.8112. This doesn’t mean IE is more secure than Chrome, but shows XSS can be, at least, mitigated if enough thought is put into it.

Nevertheless, I, like others posted here before, share the belief that it’s not the browser’s but the web developer’s job to prevent XSS attacks.

Great article. I have a question concerning the last bypassing technique. Is it possible to combine the last updated one (void..etc.) with another bypass technique like putting “> in front of the opening script tag? Or will the javascipt engine get confused?