Monday, April 21, 2014

The battle against self-xss

In the past few months I've been helping fight a rather interesting attack vector dubbed as "self xss" by the Facebook security team. It's been a rather fun journey.

What is XSS?

XSS, or Cross-site scripting, is a category of attack where the attacker is able to inject JavaScript code onto a page viewed by others. A very simple example would be if Wikipedia allowed the script tag to be used in wikicode. Someone could edit a malicious script onto the page that logs the current user out (or worse).

Most modern XSS vulnerabilities have to do with improper sanitization; though in the past there used to be browser bugs related to XSS.

What is self-XSS?

Self-xss is when the users themselves serve as the attack vector; they willfully copy untrusted code and execute it (via the JavaScript console). This is a social engineering attack which is becoming increasingly common on sites like Facebook. The basic mode of attack is to convince the user that the code does something awesome (e.g. gives access to a hidden feature), and the users do the rest. With social networking sites, the code can even re-share the original post, leading to an exponentially increasing footprint.

There's a nice video explanation of the attack from one of Facebook's engineers here. An example of the attack being used on bank websites is here.

The battle

In May 2011, Chromium landed a fix that strips the javascript: from javascript URLs pasted (or dropped) into the omnibox, and Firefox landed a fix that stopped such URLs from being used from the URL bar at all. This (partially) fixes the attack mentioned in the video, though for Chrome it is possible to ask users to do something convoluted like "type j and then paste" to get it to work. This doesn't make Chrome's solution impotent, however -- more on this later.

After a while, scammers switched to the javascript console for their attacks. This went on for a while.

In July, discussions started on Mozilla on how to fix this for the console. One prominent solution was to use the Content Security Policy (CSP) to let websites ask the browser to disable the console. More on this on a blog post by Joe Walker here.

(CSP lets websites ask the browser to disable some features, like cross-origin script loading. With this the site can greatly hamper XSS and other similar attacks provided that they structure their own code to follow the CSP.)

For a while, discussions went on (tree of relevant bugs, if you're interested), though as far as I can tell nothing concrete was implemented.

In February 2014, Facebook used a modified version of this trick to Chrome's console and enabled this change for a subset of the users. When one opens the console, one is greeted with this message:

From Stack Overflow, by Derek
trying to execute any code would result in the error message at the bottom. Fortunately, the link given there gave developers the ability to turn the console back on. (Netflix later copied this "feature", unfortunately without the opt out)

The loud text is not a bug, Chrome lets one style log messages. But the fact that the website has the power to (absolutely, if they wish) disable the console is a bug; websites should never have that level of power over the browser. I reported it as such soon after. I also noticed a need for a solution to self-xss; this was not the correct solution, but there seemed to be scope for a solution from the browser's side. I noted that in the bug as well.

Once the bug was fixed, the Chrome devtools team recognized that self-xss was something that might be fixed from the browser side, and converted the bug to one for self-xss. They also came up with a brilliant proposal (copied from the comment):
  • If user is considered a "first-time" user of devtools (a console history of less than 10 entries)
  • and pastes javascript into an execution context (console/watches/snippets) 
  • Chrome detects that and throws up a confirmation prompt, something like… "You may be a victim of a scam. Executing this code is probably bad for you. [Okay] [I know what I'm doing, continue]." (This part of the proposal was modified to having a prompt which explains the danger and asks the user to type "always allow" if they still wish to continue)
The proposed fix for Chromium

This fix was checked in and later rolled back; they're now considering a universal "Developer Mode" preference that comes with the appropriate warnings. I personally don't really agree with that change; when it comes to such attacks, a specific warning is always better than a general "Only do this if you're a dev" message. People being convinced by a scammer to inject code into their own browsers probably will click through a generic message — after all, they know that they are doing developer-y stuff, even if they don't actually know what they're doing.

On the Firefox side, I filed a bug suggesting a similar change. A while later, Joe wrote another post delving deeper into the issue. The post frames the problem by first modeling self-xss as a "human script execution engine" (the model changes later),  and notes that the more complex the "script" is, the less likely the engine is to execute it. There's an interesting analysis as to how the trend probably is, culminating in this graph:

Taken from Joe's blog, with permission
("script" here is the English script used to scam users, not the actual code)

While we can never completely defeat this, some small increases in the necessary complexity for the "script" can go a long way. (This is the reason that Chrome's solution for the omnibox is still effective. It can be bypassed, but it makes the "script" more complex with the "type j and then paste" instructions)

We just have to balance the solutions with the annoyance to devs factor.

Turns out that Chrome's solution (for the console) seems to be pretty balanced. People will be shown a prompt, which they will have to read through to figure out how to enable pasting. They can't just ignore it and press the nearest "ok" button they see, which would have been the case with a normal dialog. For devs, this is a minor annoyance that lasts a few seconds. Additionally, if the dev has already used the console/scratchpad in the past, this won't come up in the first place since it is disabled after 10 entries in the scratchpad/console.

Of course, the scammer could simply ask the victim to type "allow pasting", but this has two issues. Firstly, it's now Firefox-specific, so they lose half their prospective victims. This can be fixed by making two branches of instructions for Chrome and Firefox, but that still increases complexity/suspicion. Secondly, the flow for this is rather strange anyway; most would find it strange that you have to type "allow pasting", and might be curious enough to read the popup to see why. There's also a big friendly "Scam warning" header, which can catch their attention.

I wrote the patch for Firefox, this is what the current UI looks like when you try to paste something into the console or scratchpad:

Firefox's solution, for both the console and scratchpad

This got checked in today, and will probably reach the release channel in a few months.

Hopefully this takes a big bite out of the self-xss problem :)


  1. As the Chrome omnibox owner, I REALLY hate Firefox' disabling of javascript: URLs from the address bar. I frequently use that feature when testing, and the fact that there's no way around the Firefox block, PLUS there being no UI to even remind me that this is blocked (instead, it silently does nothing), drives me up the wall.

    Please consider changing to Chrome's solution, which is annoying, but not crippling.

    1. I personally use both browsers, and I use the console in both cases for testing JS. I've never used javascript: URLs for testing, even when they worked everywhere.

      But I can see that others might want to. If you can make a good case for why the URLbar is better than the console, feel free to post here: (The whiteboard there mentions the console as the alternative to the lost feature). There also is a request for adding a pref to control this behavior:

      I myself am not a Mozilla employee, while I have a say, it's better if you directly argue your case :)

    2. This is great stuff Manish. Thanks for all the great work.

      Peter, if you open a bug in bugzilla we can talk about it there. I'm curious why you wouldn't resort to using something like the Console or a Scratchpad for evaluating JS. It's what they're for.

  2. While it is okay for you to report the bug, it is painful to see incorrect/missing references in your bug submission as well as blog posting. If you look/read carefully in stackoverflow, it also mentions a link from my blog which was dated way back in Feb 2013. Infact your post also mentions that facebook found way in Feb 2014. As a bug filler, you should be having complete information about it and atleast made an attempt to read through the links (stackoverflow etc) and searched over it.

    So much for open world! Phew!.

    1. No need to be abrasive.

      I knew about that link, and I've known the trick existed before (I know that a workaround to Chrome's recent changes exists as well, though I haven't reported it yet*). I put that date because that was the date Facebook used it, the date relevant to this post. I worded it badly, though, sorry about that. I meant that that was the date that the xss-prevention in that form (via console-disabling) first surfaced.

      I'll edit this to clear out any confusions.

      * Some chromium devs know this too, the thing is that it's not being misused yet so we haven't looked deeply into it. The only way to completely fix this is to write the console execution code in browser-level JS instead of injected window-level JS, which seems to require some largeish changes. I'll try to come up with a testcase and report it if you think we should move forward with this.

    2. Like I mentioned in my comment, it is painful and my words are indicating that. (not harsh if you think so).

      I do not know if any individual/organization took the code from my post and replicated in their env. They are free to do so if they wish. I also do not know if someone knew this trick before the post.

      All I noticed was the bug submited based on the similar kind of work and a followup post from you, which had no mention of the link, referencing organization with a recent date.

      I hope you do understand when I say "so much for open world". Thanks. Stay Awesome.

    3. Hm, I thought I had replied to this. Must've gone to the bitbucket.

      I still don't see why you find it necessary that I should have credited your post there (even though I have now). I don't see what you mean by an open world, here, either. I was talking about an exploit in Chrome, which was used by Facebook. They discovered it, which doesn't necessarily mean they were the first discoverers. (And they might have even discovered it from your post). I don't know. There might be other, older posts on this exploit as well. Should I credit all of them? I'm not taking the credit for the exploit at any point here. I'm not misdirecting it either. I didn't want to spend time researching the origin of this exploit; it's not so relevant to the post (the exploit did start a chain of events, but what I'm more focused on is the solution and the theory behind it). I don't see any lack of openness here, basically.

      Anyway, I guess I should drop it now.

  3. A couple of comments on the proposed Firefox UI:
    - For the scratch pad it's impossible to tell *where* you should type 'allow pasting'.
    - I suppose these are in response to actually trying to paste - why don't you simply copy Chrome's UI and have a modal dialog? That way it's completely clear where to type.
    - The warning text is completely monotone, and thus makes me not read it at all. You should highlight "Scam Warning" (at the very least by making it bold).

    1. For the modal bit, this was extensively discussed and the team doesn't want any modal dialogs. (This seems to be a general firefox desig decision, and the number of modals are being reduced)

      I'll bring up the bolding bit. Have any suggestions for an alternate wording fr scratchpad? We changed it to "below" to try and make it clear, but it might not be as clear.

      (Feel free to post additional commentary on the bug itself, will help others pitch in:

  4. Actually I had tried adding scripts inside the page many time but I had never thought that I would be able to do that much things. Till yet I'd just tried a command alert saying 'Hi There!'. lol

  5. This comment has been removed by a blog administrator.