Cross Site Scripting Joy

by Andrew BettsDecember 4th, 2007

Google for ‘cross site scripting‘ and you’ll get a plethora of articles and tutorials about vulnerabilities, loopholes, and exploits. Early in the development of JavaScript it was realised that client-side scripting had the capacity to access information in other browser windows that might be sensitive and which it certainly had no business reading. This was considered a problem and dubbed cross site scripting (thankfully abbreviated to XSS, not CSS). The basic security principle that solves this is the Same Origin Policy, which prevents scripts from accessing resources unless they come from the same host. Sounds simple enough, but modern XSS exploits are incredibly complex, getting around the same origin policy by taking advantage of opportunities to inject script into websites that simply redisplay input without encoding it first.

So the battle over XSS as a security problem has moved on from the same origin policy, but same origin remains a massive obstacle to development of useful non-malicious services, and that’s particularly true of Comet, because there are typically two servers involved in any comet setup: a web server like Apache, and a comet server like Meteor or Orbited.

There are essentially three choices for making these two servers play together:

  1. marry them: have one server that serves both your Comet connections and the standard ones (including any dynamically generated content);
  2. have a regular web server with a Comet server sitting in front of it, so all connections are made to the Comet server, and it proxies the non-comet connections to the web server;
  3. have both the Comet and the regular web server exposed to the web, and request applicable content from each one.

There are very few, if any, web servers that are capable of doing Option 1 efficiently (hence the development of Meteor, Cometd, Orbited, Lightstreamer etc). Option 2 is easy and works, but is a bit of a cop out from the same-origin problem, and puts a lot of unnecessary load on your Comet server.

Which brings us to Option 3, and the need to have content served from two different sources interacting on the same page, and leaves us at loggerheads with the same origin policy.

If only the same origin policy was uniformly implemented, this might not be such a problem. But it’s not. And after getting utterly frustrated with inconsistent behaviours between different browsers, I decided to write a few tests. And I got slightly carried away, so I ended up with 38. Here are the results—I threw in an iPhone for colour:

T’port Configuration IE 6/7 FF 2 Op 9 Saf 2 Saf 3 iPhone
XHR one-part, same-origin Yes Yes Yes Yes Yes Yes
one-part, same host, different port Yes No No No No No
one-part, parent host, same port No No Yes2 No No No
one-part, parent host, different port No No No No No No
incremental, same-origin No8 Yes Yes No3 No3 Yes
incremental, same-origin, 1K prepended ‘noise’ No8 Yes Yes Yes Yes Yes
incremental, same host, different port No8 No No No No No
incremental, same host, different port, 1K prepended ‘noise’ No8 No No No No No
incremental, parent host, same port No No Yes2 No No No
incremental, parent host, same port, 1K prepended ‘noise’ No No Yes2 No No No
incremental, parent host, different port No No No No No No
incremental, parent host, different port, 1K prepended ‘noise’ No No No No No No
T’port Configuration IE 6/7 FF 2 Op 9 Saf 2 Saf 3 iPhone
IFRAME one-part, same-origin Yes1 Yes1 Yes1 Yes1 Yes Yes
one-part, same host, different port Yes1 Yes1 No No Yes1 No
one-part, parent host, same port Yes1 Yes1 Yes1 Yes1 Yes1 Yes1
one-part, parent host, different port Yes1 Yes1 No No Yes1 No
incremental, same-origin No3 Yes1 Yes1 No3 No3 Yes
incremental, same-origin, 1K prepended ‘noise’ Yes1 Yes1 Yes1 Yes Yes Yes
incremental, same host, different port No3 Yes1 No No No3 No
incremental, same host, different port, 1K prepended ‘noise’ Yes1 Yes1 No No Yes1 No
incremental, parent host, same port No3 Yes1 Yes1 No3 No3 Yes
incremental, parent host, same port, 1K prepended ‘noise’ Yes1 Yes1 Yes1 Yes1 Yes1 Yes
incremental, parent host, different port No3 Yes1 No No No3 No
incremental, parent host, different port, 1K prepended ‘noise’ Yes1 Yes1 No No Yes1 No
T’port Configuration IE 6/7 FF 2 Op 9 Saf 2 Saf 3 iPhone
SCRIPT one-part, same-origin Yes Yes Yes Yes Yes Yes
one-part, same host, different port Yes Yes Yes No Yes No
one-part, parent host, same port Yes Yes Yes Yes Yes Yes
one-part, parent host, different port Yes Yes Yes No Yes No
any incremental-loading configuration No No No No No No
Shorten document.domain Yes Yes Yes Yes Yes Yes
Lengthen document.domain to original length once shortened Yes No No No7 No7 No7
T’port Configuration IE 6/7 FF 2 Op 9 Saf 2 Saf 3 iPhone

Table notes

  1. IFRAME can only be accessed if its document.domain matches the parent frame’s document.domain
  2. XHR can only be made if document.domain is a match for or a parent of the target host. This will always be the case for XHRs to the originating host, but document.domain must be shortened to allow XHR to the parent domain.
  3. Safari and IE have a 1K buffer which must fill up before any response is parsed. Data received before the 1K mark is reached will not be rendered or interpreted and will not fire the Interactive state of an XHR until the buffer is full or the connection is closed. The obvious solution to this is to send 1K of ‘noise’ at the start of any response that needs to be parsed incrementally.
  4. SCRIPT loading is a blocking action in Opera and a non-blocking action in other browsers.
  5. In Safari, HTTP URLs that do not explicitly define a port number (and therefore default to port 80 in all browsers) are considered to have a different port to those URLs that do explicity define port 80, even though both URLs actually use the same port. The test suite therefore defines ’same port’ as ‘no port specified’, rather than explicitly setting :80.
  6. Despite setting cache-busting headers, you must give every script you load using the SCRIPT transport a unique URL, else the browser may simply pull the existing script out of memory and run it again.
  7. Fails silently (does not throw an error).
  8. In Internet Explorer, the first incremental response to XHRs does fire a readyState 3 ‘interactive’ event, but neither the body nor the headers of the response are available until the response is complete, making these events essentially useless. So for the purposes of incrementally downloading data into the browser, the incremental XHR is considered non-functional in IE.

The full results, methodology and online test suite is available on the Meteor site. Feel free to check these, tell me if I’ve got any wrong, and suggest tests that should be added. And if your browser isn’t listed, run it through all the tests, send me the results, and I’ll add it to the table.

13 Responses to “Cross Site Scripting Joy”

  1. Jacob Rus Says:

    Note that if you use an uncommon Content-type (i.e. not text/html, etc.; Orbited uses application/x-orbited-event-stream for XHR streaming), then Safari only needs 256 bytes of padding before incremental rendering begins.

  2. Jacob Rus Says:

    That is, instead of the 1024 bytes you mention here. :)

  3. Jacob Rus Says:

    Also, I think IE only needs 512 bytes before incrementally rendering in an IFrame, but I’m not sure of that one.

  4. Kris Zyp Says:

    Fantastic information. The one result that seems different than I have experienced is on the same-host cross-port XHR on IE. In IE, I always get an “access denied” when trying to do an XHR to the same host with a different port. How are you able to do this? Not that it is really important, since none of the other browsers support it…

  5. AndrewBetts Says:

    Jacob - I’ll refine the data accordingly. I did think of going on to work out exactly what the buffer is, but I’d sunk a fair amount of time into this by then, and figured someone had probably worked it out already :-P

    Kris - I went to some lengths to try and isolate each test case, so it may be that you’re hitting some other dependent issue? You can try the tests yourself and see the source at http://xsstest.meteorserver.org - it’s not minfied. Cross port in IE definitely works, not that it gets you anywhere :-)

  6. Martin Tyler Says:

    Interesting work Andrew.

    The iframe tests dont seem to be working though, I get Failed every time in FF and IE. Looking into the incremental ones with firebug looks like there is a missing script tag and/or repeated chunk of script.

    Anyway. I find that IE handles incremental updates with no noise just fine… it needs a small amount of noise at the start of the file only, then subsequently any closing script tag will cause the code to be run.

  7. AndrewBetts Says:

    Martin - the iframe tests won’t work in IE or FF until you shorten documnet.domain, because all the iframes set their document.domain to meteorserver.org on load. This is to allow testing with various values of document.domain in the parent frame (note that Safari can access the iframe despite the document.domain not being the same - one of the most surprising things I doscovered)

    Regarding the IE buffer - what do you call ‘a small amount of noise’?? Half a meg? :-)

  8. Martin Tyler Says:

    about 250 bytes actually

  9. DylanSchiemann Says:

    Martin, interesting and neat. I’ve always just relied on a <br> tag to “flush” IE and had assumed it was a dependency on forcing IE to render something.

  10. Tom Robinson Says:

    This article is about the same origin policy… not XSS…

  11. Jacob Rus Says:

    Dylan: I think any closing tag will force it to render an incremental update.

  12. Michael Carter Says:

    Andrew, I think your tests for cross-port stuff are not all correct. I’ll look more closely at all of them, but I know for sure that “IFRAME: incremental, parent host, different port” works in IE 5.01+ … in fact I previously emailed you a demo of this very test.

  13. AndrewBetts Says:

    Michael - you’re right about Incremental IFRAME+different port working in IE, but your test includes padding at the start of the response, as does the version of mine that works in IE. So the failure on that test and success on the subsequent one shows that IE does not evaluate the response until it’s filled its buffer, which Martin Tyler reckons to be around 250 bytes - I just used 1K for my tests.


Copyright 2015 Comet Daily, LLC. All Rights Reserved