I was pleased to discover that the jQuery UI library includes an
autocomplete widget
that looks good and works well, and I started using it straightaway to
liven up my search boxes. The simplest way to use it is to return an
array of JSON objects matching a query from /some/url, and feed the
client the following bit of Javascript:
$('#query').autocomplete({source: '/some/url'});
All was well, until I came across this paper on
Javascript Hijacking.
A quick summary: someone includes a <script> tag on their web site
pointing to http://your.serv.er/that/url, and write some Javascript to
steal the data (by redefining the array constructor, or overriding the
property setter for objects). Due to the very specific circumstances of
this attack, it is often misunderstood or ignored as "just another XSS
attack", but it can lead to leakage of confidential data. The attack was
originally demonstrated by Jeremiah Grossman to steal contacts from
someone's GMail account.
The paper outlines a few possible solutions: deal with it like any other
cross-site request forgery by requiring the request to include a random
unguessable (which means using POST or including the token in the URL),
or prefixing the response with "while(1);" or some other statement that
would cause the browser to fail to execute the code (e.g. wrap it inside
a comment).
Tony Cook pointed out to me that the attack hinges on the fact that an
array literal is a valid statement in Javascript, but an object literal
by itself is not. This suggests that returning the matches as an object
rather than array will defeat the attack:
{"matches": [{"label": "one", "value": 1}, ...]}
Although I can't be certain that no Javascript implementation will treat
this as a valid statement, I like this approach better than using POST
to fetch a completion list or extending CSRF protections to GET
requests. Fortunately, jQuery UI's autocomplete widget is more than
flexible enough to accommodate an array of matches wrapped in an object:
$('#query').autocomplete({
source: function (request, response_cb) {
$.getJSON(
'/some/url', {term: request.term},
function (data) {
response_cb(data.matches);
}
);
}
});
Unfortunately, this is already much more code than people should have to
write to handle this case, and it doesn't even handle errors (if any do
occur and are not properly handled, the autocomplete widget will behave
strangely). I think jQuery UI should handle this case by default.
This code can easily be adapted to cope with a "while(1);" prefix—treat
the response as a string and remove the prefix, then eval the remainder
and pass the resulting object to the response callback. If someone can
convince me that an object literal is insecure—something of which I am
quite willing to be convinced—then I'll change my code to do that. (If
this seems a too-casual attitude, it's because none of my autocompletion
data are confidential. Defeating casual attacks is fine for the moment.)