Monday, July 16, 2007

Cross Site Scripting with Yahoo Local Search API

So I have a fun a little project where I was playing around with the Yahoo Local Search API. I really like Yahoo's local search. I think it is much better than Google's, though in all fairness that opinion was formed 18+ months ago. They have a great open API. Of course they place a limitation on how much you can use. What's interesting is that usage is tied to IP address. The limit is 5000 queries per day per IP address.

This of course begs for Cross Site Scripting(XSS.) If the call to their API to happen from a user's browser, then it counts against that individual user's 5000 query limit. One obvious way to accomplish is this is with Flex. That's because the Yahoo API server farm has an open policy for Flash.

Another way is via JavaScript. Of course I can't make an XMLHttpRequest directly from a user's browser to Yahoo because of browser security. Luckily Yahoo encourages Cross Site Script. You can make a request to their local search API with response output set to JSON and with a callback method. From there you just need some sneaky JavaScript:

 function search(lat,lng,cat){
var reqUrl = "http://local.yahooapis.com/LocalSearchService"+
"/V2/localSearch?appid=YahooDemo&"+
"output=json&callback=showResults";
var req = reqUrl + "&query="+cat+"&latitude="
+lat+"&longitude="+lng;
var rdiv = document.getElementById("resultlist");
var jsonp = document.createElement("script");
jsonp.setAttribute("type","text/javascript");
jsonp.setAttribute("src",req);
rdiv.appendChild(jsonp);
}

The sneaky part here is creating a "script" DOM element, setting its src attribute to the dynamic URL, and then dropping it into the main page DOM. It gets evaluated immediately. The URL response looks like:


showResults({"ResultSet":{"totalResultsAvailable":3423,"totalResultsReturned":10,"firstResultPosition"

:1,"ResultSetMapUrl":"http:\/\/local.yahoo.com\/mapview?stx=coffee&radius=50&ed=sh5iF6131DyGMtZvcmi9jNSxMpMwUjolfcSc44DLrkDbXpY-"

,"Result":[{"Title":"City Espresso Gourmet Coffee","Address":"630 Blossom Hill Rd","City":"San Jose"

,"State":"CA","Phone":"(408) 972-4500","Latitude":"37.250463","Longitude":"-121.8436","Rating":{"AverageRating"

:"","TotalRatings":"0","TotalReviews":"0"},"Distance":"1.18","Url":"http:\/\/local.yahoo.com\/details

?id=21609154&stx=coffee&csz=San+Jose+CA&ed=WbZ4Ba160SzTaXFlbDSmVAyxneC9N59mhMtS3xZb6kL86aeI4P1iTZT5BJOOXR5qK9HSoV5KX0wL"

,"ClickUrl":"http:\/\/local.yahoo.com\/details?id=21609154&stx=coffee&csz=San+Jose+CA&ed=WbZ4Ba160SzTaXFlbDSmVAyxneC9N59mhMtS3xZb6kL86aeI4P1iTZT5BJOOXR5qK9HSoV5KX0wL"

,"MapUrl":"http:\/\/maps.yahoo.com\/maps_result?name=City+Espresso+Gourmet+Coffee&desc=4089724500&csz

=San+Jose+CA&qty=9&cs=9&ed=WbZ4Ba160SzTaXFlbDSmVAyxneC9N59mhMtS3xZb6kL86aeI4P1iTZT5BJOOXR5qK9HSoV5KX0wL

&gid1=21609154","BusinessUrl":"","BusinessClickUrl":""}]}});

The function showResults gets evaluated with the results from Yahoo. How nice of Yahoo to support JSONP. The eBay APIs also have great support for this technique.

2 comments:

Anonymous said...

Out of curiosity; what does this actually accomplish? Does it just drive down the 5000 requests a user has to make? The resulting object doesn't have any session/cookie information so I don't see how it could be used for anything malicious (but perhaps I am being naive).

Unknown said...

For my purposes, the point is exactly to drive down the 5000 request limit. It basically removes the limit, since all requests will be tied to an end user's IP address, not to the IP address of a web server. It's great the metering is not tied to an API key.

As for malicious uses... I guess that's what people first think of when it comes to cross site scripting. That certainly wasn't my intention. I think Yahoo clearly wanted to enable XSS for exactly the kind of thing that I did. XSS gets a bad name, but there's nothing inherently evil about it.