The other day I was wondering how Google Analytics works… You put a few lines of Javascript into your page and it loads another script to send data back to one of its servers. I assumed they used an AJAX XMLHttpRequest to send stats to their server. However, an XMLHttpRequest created in Javascript (or any script language for that matter) can only make calls back to the server where the browser made the original request, not where it loaded the script. So if for example a site abc.com has a page which loads a script from def.com, the script can create an XMLHttpRequest object but it can ONLY make calls to abc.com.
Hmmm… that’s not entirely true. With IE 6.0.2900 and probably earlier versions of Firefox / other browsers it was possible to make the XMLHttpRequest object call any server, which would be great for a Denial of Service (DoS) attack. Imagine you have a site with millions of page views a day? And your’e feeling unfriendly and want to create a DoS attack against your foe. Easy, you put some script in a page which is frequently viewed, and unknowingly, every one of your readers viewing that page makes a call to your foe’s website, stretching its abilities to relpy to all requests, resulting in the DoS attack, which might even take down the server… Or even worse, an unfriendly user of yours posts some content to a forum containing a script to perform that DoS on their own enemy!
Luckily, that security issue was fixed in IE 7 and Firefox 2 (at least the versions I tested on which included service packs). But there is another reason why it is dangerous for an AJAX client to be able to call any server: Scripts have access to cookies and the URL, which means they have access to session IDs. If scripts can send data anywhere, they can easily hijack the session ID and pass it back to a malicious server. As a reminder, a session ID works like this: a user makes a request to view a page on a server. The server checks for a session ID in a cookie or the URL. If its not present, creates a unique ID and sends it back to the user, and probably sends them the login page instead of the original page they wanted to view. Once the user sends their login credentials and the session ID back again, the server checks their credentials and remembers that the user is authenticated and only relies on the session ID being around in every call which the user makes. So anyway, a script can send the cookies and URL to a malicious server which in turn can make its own request to the original server, pretending to be that user. Such an attack is known as a cross site scripting (XSS) attack. Theoretically, the malicious server could change the password, transfer funds to its own account, make forum postings on the users behalf, etc. Whether the connection between the user and the original server is secured over SSL is irrelevant because the script is running inside the users browser.
But how does a malicious script get put on a site in the first place? Its not like the author of a site would go to those lengths, since his server has the session ID anyway, and the user is one of his customers! Well luckily, this is the hard part, and as a systems designer, you can still protect yourself against such attacks. Lets say someone has the intent to create such an attack. They need to somehow get the script into a page which other people can view. To do that, they would look for a forum or page where they can make a posting. So most corporations like banks or online shops are already protected because they don’t allow such posting, although it has become trendy for customers to be able to post reviews, like on Amazon.com.
Hold on though, two paragraphs ago I said an AJAX client could NOT make calls like this, to send session info to a malicious server. But somehow, Google Analytics sends that kind of data back to its servers, and thats where it got interesting – how do they do it? I had a go at reading the Javascript which Google uses, but its been obfuscated and instead of using meaningful names for their functions, they all have simple letters for their names. I did get as far as noticing that they seem to be loading some Flash, so I assumed that Flash has a model like Java Applets in which they can send data back to the server from which they are loaded. So I wrote a tiny applet and posted it to my own site. The applet does nothing more than send a string back to the server where the applet was loaded from. Note that applets are restricted to only talking to the server from which they originated, so they cannot be used for DoS attacks. The next step was to write some Javascript which loads that applet, passing it a string parameter, which contains the URL from the browser as well as all the cookies. I then posted that script to a forum (my own I hasten to add – I’m not in the business of hacking peoples accounts!). To post the script, I logged in as myself and created a thread with an enticing name and some provocative text (I want lots of people to read it so I can steal their sessions!). As well as the provocative text, I included the Javascript to load the applet and pass it the cookies and URL. Since browsers don’t show script to the user, they don’t know whats happening.
I then logged in as a different user on a different browser and read the bogus posting. Indeed, the applet posted the session info back to my server. A few milliseconds after "the user" had read the provocative thread, I had their session ID. Whats more, I’d already programmed the malicious server to make a posting on behalf of the different user, telling the world that the site had been hacked 🙂
I nice little experiment I thought, but let me repeat, this was all done on my own servers, in fact locally on my laptop – no ones accounts are in danger! The point is, that its VERY easy to steal session IDs, if there is a way to make a posting that contains script. So by default, my bank is NOT open to such an attack, which makes me happier about using their online services. Even if it were, they have gone to the extent of requiring me to retype parts of my password if I want to transfer funds to a different account, although as I’m writing this I don’t think that’t the case when I pay bills…
By far the best protection against such XSS attacks is to strip out any script and simply not allow it to be posted. Lots of big sites do that (e.g. Mediawiki doesn’t even let you post HTML, they have their own format). I have read that Google even has their own parsers for stripping malicious code out of postings, which shows how important they think it is to protect the world against such attacks.
Other forms of protection I have seen as to put all postings inside an "IFrame" – a special HTML tag which loads content from a site inside a frame protecting the rest of the page from that content. In my tests, code running inside an IFrame still had access to some of the cookies from the main outer page, so if a session ID is contained in cookies, its still not safe. It was interesting to note that Firefox 2 provided some of the cookies while IE 6 didn’t provide any.
The final way to protect your sites users is to get them to provide parts of their password again, when they are doing anything important, like making payments or postings. That way, a malicious script has no chance of spoofing their actions.
Copyright (c) 2008 Ant Kutschera