While looking at the response data from certain AJAX requests in GMail, I noticed that they were prepending their response JSON with either a number, “while(1);”, or “)]}'”. They do this to avoid cross-site inclusion attacks (XSSI), where a malicious site includes a file from another site in order to steal its data. Example:
<-- Code on evil site -->
<script src="http://gmail.google.com/user_email.json"></script>
Even if the data in the JSON object isn’t wrapped in a function or set to anything, it can still possibly be stolen by overriding the Array constructor or defining a setter property on the Object prototype. Most of these holes were plugged up after being discovered, but they still exist in some older browsers, which is why GMail is putting these safe guards in place. Two of the techniques of the mentioned, “while(1);” and “)]}'”, are pretty straight forward. “while(1);” will cause an infinite loop, keeping an attacking site from reaching its payload, and “)]}'” will cause a syntax error, stopping the rest of the script from executing. But the number trick is not so straight forward, and it had me scratching my head. The response data looked something like this:
123
["Email message", 123, "?", 0];
Was this to cause a syntax error? I decided run the code – nope, no syntax error. In fact, it turns out that the above code is valid JavaScript. However, instead of returning the last line, the array, it returns undefined. Wait a second, how could that possibly evaluate to undefined?
To experiment more, I decided to try executing:
123
456;
Which evaluates to 456. And then:
123
"test";
Which evaluates to “test”.
So when the second item is an array, it evaluates to undefined, but when it’s a number or string, it evaluates to the second parameter? WTF JavaScript? After showing the issue to a few co-workers, none of us were able to figure out what was going on, so I took the question to StackOverflow.
Within a few minutes, freakish had the answer. It turns out that JavaScript’s automatic semi-colon insertion was to blame. In the second and third cases, JavaScript realized that “123 456” and “123 ‘test'” weren’t valid statements, so it inserted a semi-colon at the line breaks. However:
123 ["Email message", 123, "?", 0]
IS a valid statement. What’s happening is that the []’s cause JavaScript to look for a property value on the number object. The comma statement returns its last parameter, 0, so JavaScript looks for a property named 0 in the number’s prototype chain. When it finds nothing, it returns undefined. This can be tested with the following code:
Number.prototype["0"] = "test";
123["Email message", 123, "?", 0]
Which evaluates to “test”.
Wow. That is so completely ridiculous. However, it’s also pretty clever on Google’s part. It allows them to have another XSSI-busting technique, and I’m assuming they use a variety of methods in case a hack is discovered which gets around one of the other techniques. Anyway, I found the number trick to be especially interesting, so I figured I’d share it here. And again, freakish deserves the credit for figuring out what was going on.