The last few days have seen a very widely reported news story stating that:

“Access to hundreds of thousands of Facebook accounts may have accidentally been leaked because of a flaw in some applications.”

Source: http://www.bbc.co.uk/news/technology-13358293

 

The original story came from the security company Symantec. Mainstream and technology media have taken the story and somewhat sensationalized it. There is some truth in it, but there’s also a healthy dose of scare-mongering and surprisingly little advice about what you can do to protect yourself beyond changing your password.

Based on the media coverage,  you may have concerns about our Facebook application, “The Big 5 Personality Experiment”, so let me explain what’s going on and what you can do about it.

 

Did our application expose you to this risk?

The short answer is ‘No’.  Our application wasn’t susciptable to this security issue in the Symantec report for two key reasons.

  1. We didn’t use the settings identified in Symantec’s report.
  2. We also use something called server-whitelisting. This means that only servers in that list can use the access token you provide.  If the token fell into the wrong hands, it would be useless.

I you’re interested in a more technical appraisal of why our application wasn’t susceptible to this risk, keep reading.

Now, that’s all very simplisitic, but if you haven’t taken advantage of recent security enhancements in Facebook, you *could* still be at risk when you use Facebook (not just our application) for anything, even if you have no applications.  So before we look at the technical details, here’s what you can do right now if you’re not sure what security settings you’ve selected or might have had in the past.

 

What to do if you’re concerned

Step 1 : Set Facebook to secure browsing

The first thing you really should do is make sure you’re using HTTPS.  HTTPS stands for HTTP-Secure.

Here’s how you enable it.

  1. Log into Facebook
  2. Click “Account” then “Account Settings”  (see the red box in the picture below).account settings
  3. Click on “Account Security” (see the red box in the picture below).Where to find Facebook Account Security
  4. Make sure you select the “Secure Browsing (https)” box and click Save.Facebook Account Security setting to select HTTPS

This step alone will significantly reduce your risk to unwittingly exposing your Facebook details (whether you use applications or not) to other people.

 

Step 2:  Change your Facebook password

The quick solution is to change your Facebook password using steps 1 and 2 from above, but choosing “Password” in Step 3 (see image below)

change facebook account password

 

Simple.


Note: If you’ve only ever installed our application, then you’re as safe as you were before the article came out.  This DOES NOT mean that you’re 100% safe, just as safe as you were before.  If you’re not using Facebook Secure Browsing (HTTPS), now is a good time to use it.

As mentioned, we didn’t suffer the problem outlined in the Symantec article. If you’d like to know more about why we didn’t suffer the problem, read on, but it does get a little bit technical (you might also find this interesting if you’re trying to understand how Facebook iFrame applications work).

Read on for the technical bit...


A Technical Analysis of how we handle your access tokens & why we didn’t suffer this problem

“Symantec has discovered that in certain cases, Facebook IFRAME applications inadvertently leaked access tokens to third parties like advertisers or analytic platforms.”

source: Symantec’s original Blog article on the subject

To be fair, they do say “in certain cases”, which is true.

If our interpretation of the numbers which Symantec are stating are accurate, 100,000 applications (out of roughly 550,000 applications) request access to your Facebook data while you’re not logged on. This is called “offline access” and has many legitimate uses, in fact we use it in our application to collect data even if you happen to log out while we’re still collecting it.

Out of this 100,000, some unknown percentage of applications may actually have had a problem.  Here’s a pie chart to put this in perspective (you can try this yourself in your favourite spreadsheet, it really does look like Pacman):

 

Graph showing the number of Facebook applications which were possibly at risk

Graph showing the number of Facebook applications which were possibly at risk

Facebook access token are NOT like spare keys (Not really anyway)

“Access tokens are like ‘spare keys’ granted by you to the Facebook application”

source: Symantec’s original Blog article on the subject

In order for most applications to provide you with the experience you want, they need access to some of your Facebook data.  This is not the same as having access to your facebook account.

Keeping with the key analogy, your Facebook password can be thought of as a Master Key; if someone has your password, they can log on as you and do all sorts of damage.

Access tokens, on the other hand, are more like keys that allow access to some rooms but not others.  Further to that, unless the application has requested offline access, these keys stop working (expire) after a relatively short period of time. What someone can do with that key depends on the permissions you granted the application when you installed in.

Here’s a picture of the permissions screen for our “Big Five Personality Experiment’ application.

Facebook Permissions Page for Big Five Experiment

Offline access can still be considered a key (but not a master key), but one that doesn’t expire until one of two things occur.

  1. You remove the application
  2. You change your password (this is new functionality and to me, a bit perplexing, but that’s another story)

 

Here’s what the offline_access permission looks like in the permissions dialog.

Facebook Offline Access - access my data at any time

NONE of these keys gives a third party access to your password!

 

The possibility that someone could intercept this access token (that we use legitimately) caused us great concern from day one.

So how do we handle this?

 

What happens when you add and use a Facebook iFrame application

Here’s a  simplified process of what happens when you add and launch the ‘Big 5 Personality Experiment‘ application. By simplified, I mean that I haven’t included every single flow (e.g. HTTPS handshakes).

Assumptions / Configuration:

  • The user has enabled “Facebook secure browsing (https)”
  • The Facebook iFrame  app is set up with
'appId'  => FACEBOOK_APP_ID,
'secret' => FACEBOOK_SECRET_KEY,
'cookie' => false,
'domain' => 'onlineprivacyfoundation.org'

This means our the getSession won’t bother looking for cookie support and will just grab session data from signed_request.  We did this because.

  • Canvas URL is https://onlineprivacyfoundation.org/Big5PE/

Step 1

When someone clicks your application (e.g. https://apps.facebook.com/bigfiveexperiment/ ) a GET request is made to Facebook (See the first arrow in Figure 1 below).

 

Initial GET request to apps.facebook.com/bigfiveexperiment

Figure 1. Initial GET request to apps.facebook.com/bigfiveexperiment

Step 2

Facebook returns a blank canvas page (see second arrow in Figure 3 at the end of this step) which includes some content to generate a POST request  to the canvas URL you specified in the application settings (see Figure 2, below).

Canvas URL

Figure 2. Canvas URL setting

Included in the POST request is something called a signed_request. A description of the signed_request is contained in the Facebook developer core concepts here and there’s a pretty neat blog post about the signed_request here. In a nutshell it looks like this once you’ve (or the PHP SDK has) exploded it:

Array (
[algorithm] => HMAC-SHA256
[expires] => 0
[issued_at] => 1305311354
[oauth_token] => 10000000002|5d04efxxxxxxxxxxe4e4.1-1000000000000002|0QCOAAAAAAAAAAAAAAAAAA_10
[user] => Array ( [country] => gb [locale] => en_GB [age] => Array ( [min] => 21 ) )
[user_id] => 1000000000000002 )

Note: The user_id is only available after the user has authorized your application. This is how Facebook determines whether it needs to show you the permissions screen or not. If the user hasn’t added your app, you’ll see this in the signed_request.

Array ( [signed_request] =>  loxxxxxxxxxxxxxxxxw.eyxxxxxxxxxxxxxxxCxxxxxxxxxeyJjbxxxxxxxxxxxxxxhxxxx19
[PHPSESSID] => 4hbxxxxxxxxxxxx0 ) Array
Array ( [algorithm]  =>
HMAC-SHA256 [issued_at] => 1305627690
[user] => 
Array ( [country]  => gb [locale] => en_US [age] => Array ( [min] => 21 ) ) )

Figure 3, below, shows this entire sequence.

How a Facebook Application Works Step 1

Figure 3. Clicking a link to a Facebook application

 

Here’s some PHP code you can take and play around with to see how the signed_request is handled.


Steps 3

In this next image (Figure 4) we see the POST (which includes that signed_request) that was embedded in the previous response being sent to the canvas URL from ‘Figure 2‘.

In this case, our page (the Canvas URL from Figure 2) doesn’t require any facebook permissions or log-on information as it’s just a static web page. This means we ignore the signed_request  and users will see this page whether they’re logged into facebook or not. You can also go directly to this page via https://onlineprivacyfoundation.org/Big5PE/ . This is pretty obvious stuff, but I figured I’d mention it.  It’s handy to know if you want to have multiple ingress points to your application or want to present the user something before they grant your application permissions.

 

Step 4

The response from the POST  (your application) is loaded into the target iFrame in the blank facebook canvas page (You’ll find something like this in the response from step 2, target=\”iframe_canvas_fb_https\”).

We can see the entire sequence and the full landing page in the diagrams below Figure 4 and Figure 5.

 

How a Facebook iFrame Application works

Figure 4. Returning the page at your Canvas URL

 

Including your facebook application in the Facebook canvas

Figure 5. How your application is rendered in Facebook

At this point, nothing happens until the user clicks one of the buttons.

 

Step 5

This step is simple, the button simply issues a GET request (and nothing else) for the next page, in our case quiz.php, but what happens next is more interesting.

 

Step 6

Quiz.php checks whether there’s a valid Facebook session for the application with this bit of PHP code (using the Facebook PHP SDK)

$session = $facebook->getSession();

if (!$session) {

$url = $facebook->getLoginUrl($params); 
echo "<script type='text/javascript'>top.location.href = '$url';</script>";

} else {

 

If there is no existing session, ‘$url’ is built using ‘getLoginUrl’ with ‘$params’ made up of the following:

$params = array(  'canvas' => 1,'fbconnect' => 0,'req_perms' => 'email,publish_stream,status_update,user_groups,user_likes,user_checkins,user_hometown,read_friendlists,user_activities,user_interests,user_religion_politics,user_about_me,user_birthday,user_relationships,user_photos,user_location,user_work_history,read_stream,offline_access',
 'next' => 'https://onlineprivacyfoundation.org/Big5PE/quiz.php',
 'cancel_url' => 'https://onlineprivacyfoundation.org/fatalerror.php');

 

This is sent back to the browser as can be seen in step 6, Figure 6 below.

 

How Facebook iFrame Applications works - login redirect

Figure 6. Login redirect when sesion = NULL

Step 7

A GET request is now made to the Log in URL built and returned in step 6. In our case it looks a little like this.

GET /login.php?api_key=1111111111111111&
cancel_url=https%3A%2F%2Fonlineprivacyfoundation.org%2Ffatalerror.php&
display=page&
fbconnect=0&
next=https%3A%2F%2Fonlineprivacyfoundation.org%2FBig5PE%2Fquiz.php&
return_session=1&session_version=3&v=1.0&
canvas=1&
req_perms=email%2Cpublish_stream....<deletia>...offline_access HTTP/1.1

It also contains all the usual Facebook cookies.

Figure 7, below, shows this.

 

GET Request to login URL built using PHP SDK getLoginUrl

Figure 7. GET Request to login URL built using PHP SDK getLoginUrl

Step 8

The Facebook login handler (www.facebook.com/login.php) does it’s stuff and if we’re logged in and have permitted the application, login.php sends a redirct (see below) and sets some cookies.

Location: https://apps.facebook.com/bigfiveexperiment/quiz.php?
session=%7B%22session_key%22%3A%22a11111111111168.0-11111111%22%2C
%22uid%22%3A%22731806265%22%2C%22expires%22%3A0%2C%22secret%22%3A%
211111111111111111111193c19%22%2C%22
base_domain%22%3A%22onlineprivacyfoundation.org
%22%2C%22
access_token%22%3A%22171111111111166%7Ca0111111111111111118.
0-7111111111111111v-Kjl1111111119J6uM%22%2C%22sig%22%3A%22111111111111111
%22%7D

This access token (highlighted in red above) is most likely (I haven’t seen the details of Symantec’s study) the offending piece of data that Symantec noticed in their research.

Looking at old Fiddler sessions from when I initially tested various settingsf for our application, I noted that (if the settings were set as Symantec mentioned) the return locations would NOT  have been..

Location: https://apps.facebook.com/bigfiveexperiment/quiz.php?

…but (as it turns out, more worringly)

Location: https://www.onlineprivacyfoundation.com/Big5PE/quiz.php?

 

So what this means is that the application provider would see POST requests like the one below, in their logs (generally not a good thing, although it depends how logs are managed).

POST /Big5PE/process3.php HTTP/1.1" 200 4628
"https://onlineprivacyfoundation.org/Big5PE/quiz.php?
perms=email%2Cpublish_stream%2Cstatus_update%2Cuser_groups%2C
user_likes%2Cuser_checkins%2Cuser_hometown%2Cread_friendlists%2C
user_activities%2Cuser_interests%2Cuser_religion_politics%2C
user_about_me%2Cuser_birthday%2Cuser_relationships%2Cuser_photos%2C
user_location%2Cuser_work_history%2Cread_stream%2Coffline_access&
selected_profiles=xxxxxx&
installed=1&
session=%7B%22session_key%22%xxxxxxxxxxxxf9d1xxxxxxxx%22
uid%22%3A%1notarealuid1%22%2C%22
expires%22%3A0%2C%22
secret%22%3A%22f9xxxxxxxxxxxxxxxxxxxxxxxx%2C%22
base_domain%22%3A%22onlineprivacyfoundation.org%22%2C%22
access_token%22%3A%221blahblahblahblah7%7Clb_lC8tSxxxxUVln-UQOxxxxx
sig%22xxxxblahblahblah91%22%7D"
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0"

 

So, guess what the referrer will be if the application pulls in pages from third parties, e.g. images from third party web sites etc ?

1x.x.x.x5 - - [02/Apr/2011:22:07:25 -0600] "POST /Big5PE/process3.php HTTP/1.1" 200 4628 "https://onlineprivacyfoundation.org/Big5PE/quiz.php?perms=email%2Cpublish_stream%2Cread_stream%2Coffline_access&selected_profiles=8xxxxxx&
installed=1&session=%7B%22
session_key%22%3A%2xxxxa7bxxxxf9d1-88xxxx%22%2C%22uid%22xxxxxxx22%2C%22expires%22%3A0%2C%22secret%22%3A%2xxxxxxxxxxxxxxxxxx7d0%22%2C%22
base_domain%xxx2onlineprivacyfoundation.org%22%2C%22
access_token%22%3A%22178457902168866%7C06fb30a7b353a2f07148f9d1-8802517%7Clb_lC8tSGsWkZnkMfUVln-UQO7k%22%2C%22sig%22xxxxxxxxxxx%7D"
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0"

 

To me, this means there are at least two issues.

  1. These access tokens are likely stored, in clear text, on application providers web server logs and
  2. The original concern cited by Symantec. To give Nishant Doshi and Symantec their credit, it’s a good catch.

This only happens with return_session in the getLoginURL in the PHP SDK is set to 1.

We had spotted this behaviour but were concerned that the URL with the access token would appear in a browsers address bar.  At the time, we hadn’t joined the dots in the way Symantec did in their study.

To resolve this behaviour, we set the return_session flag to 0 (FALSE).  If return_session is set to 0, it appears that the deprecated auth_token parameter is passed in the  request, with the signed_request in the body so you just don’t get the same problem.  Note: The signed_request contains the oauth_token which is basically the same as the access_token, but since it’s in the body, it won’t appear in referrer logs.

The auth_token really does nothing. I found the following two snippets while looking into this

“auth_token support was officially discontinued on 2010-01-12.  Support was removed from the PHP library on 2010-01-07  (r1733)

The calling the require_login function in the client library, by default, still results in auth_token params being passed back to the application.  These obviously can’t be parsed because support was removed.

The new login method requires passing return_session=true in the login URL, however the PHP library functions require_login, validate_fb_params, and get_valid_fb_params neither send this parameter to login.php by default, nor do they parse the data returned in the &session= param if it’s manually added.”

 

“Potentially Breaking Change for Canvas Applications 08 January 2010 12:04
This change has the potential to affect canvas applications only. It doesn’t affect Facebook Connect sites, desktop applications, or any type of mobile applications.

Historically, when a user who hadn’t authorized an application first visited that application’s canvas page, the user would typically get redirected to tos.php. After the user authorized the application, Facebook would redirect the user back to the application’s canvas callback URL and include a GET parameter named “auth_token.” Over time various fb_sig parameters were also POSTed to the canvas callback URL, so eventually auth_token became superfluous. After the January 12, 2010, weekly push, we will stop passing auth_token to the canvas callback URL when redirecting a user after authorization.

As long as your application isn’t using the auth_token for any purpose (for example, using it for logging or analytics), you shouldn’t experience any problems. However, if you rely on this parameter under any circumstances, there is a potential for problems. Please make sure your canvas applications never rely on being passed this parameter.”

Source : http://www.igoliat.com/forums/archive/index.php?t-134999.html

 


Here’s the getLoginURL code with the return_session flag (you can set to 1 or 0).

public function getLoginUrl($params=array()) {
 $currentUrl = $this->getCurrentUrl();
 return $this->getUrl(
 'www',
 'login.php',
 array_merge(array(
 'api_key'         => $this->getAppId(),
 'cancel_url'      => $currentUrl,
 'display'         => 'page',
 'fbconnect'       => 1,
 'next'            => $currentUrl,
 'return_session'  => 1,
 'session_version' => 3,
 'v'               => '1.0',
 ), $params)

 

Facebook login redirect including URL with access_token

Figure 8. Facebook login redirect including URL with access_token

Step 9 (This step didn’t seem to be here before the Symantec finding, so we assume it’s the fix)

A request is made using the long url, with all the problems in it, but this time it’s made to apps.facebook.com so it’s not getting shared with third parties.

 

GET redirect address with access_token in URL

Figure 9. GET redirect address with access_token in URL

Step 10

Facebook sends a second redirect, but this time without the access_token in the URL, but (this is pretty much the same as step 2) the signed_request in the body.

 

Facebook canvas (like in step 1),

Figure 10. Now there is a valid session, so rather than getting redirected to the Facebook login handler, you get the blank Facebook canvas (like in step 1), but with the signed_request will signify a valid logged in user who’s authorized the application

Step 11

Now we see the POST (which includes the signed request, but now with more data in it) being sent to the Canvas URL from Figure 2.

 

Blank Facebook canvas (like in step 1) with code to load you defined as 'next' and passed to facebook.com/login.php

Figure 11. Blank Facebook canvas (like in step 1) with code to load you defined as 'next' and passed to facebook.com/login.php

Step 12

getSession from the PHP SDK is now able to find a valid session based on the signed_request, so you get the page rather than the redirect (refresh) to www.facebook.com/login.php.

 

Blank Facebook canvas (like in step 1) with code to load you defined as 'next' and passed to facebook.com/login.php

Figure 12. Blank Facebook canvas (like in step 1) with code to load you defined as 'next' and passed to facebook.com/login.php

Step 13

In this next image (Figure 13) we see the POST (which includes that signed_request) that was embedded in the previous response being sent to the canvas URL from ‘Figure 2‘.

POST to pull quiz.php

Figure 13. POST from previous response

Step 14

The response from the POST  (your application) is loaded into the target iFrame in the blank facebook canvas page the same as in step 4.

Quiz.php being returned

Figure 14. Quiz.php being returned

 

And finally, your browser displays the quiz page.

The quiz page the Facebook user will see

Figure 15. The quiz page the Facebook user will see

 

We covered why we didn’t expose the access token, but….

What about Whitelisting?

Well, the access token is related to an application. e.g. The Big Five Experiment.

Application developers (like us) can specify which IP addresses the application may use to communicate with Facebook.  See the following screen shot

 

Facebook application server whitelisting

Facebook application server whitelisting

 

…requests from any other IP addresses are simply dropped.

This means that a sneaky third party could get the access token, but wouldn’t be able to use it, as the following example shows.

 

Here’s the access token in a log file on a web server.

POST /Big5PE/process3.php HTTP/1.1" 200 4628
"https://onlineprivacyfoundation.org/Big5PE/quiz.php?
perms=email%2Cpublish_stream%2Cstatus_update%2Cuser_groups%2C
user_likes%2Cuser_checkins%2Cuser_hometown%2Cread_friendlists%2C
user_activities%2Cuser_interests%2Cuser_religion_politics%2C
user_about_me%2Cuser_birthday%2Cuser_relationships%2Cuser_photos%2C
user_location%2Cuser_work_history%2Cread_stream%2Coffline_access&
selected_profiles=xxxxxx&
installed=1&
session=%7B%22session_key%22%xxxxxxxxxxxxf9d1xxxxxxxx%22
uid%22%3A%1notarealuid1%22%2C%22
expires%22%3A0%2C%22
secret%22%3A%22f9xxxxxxxxxxxxxxxxxxxxxxxx%2C%22
base_domain%22%3A%22onlineprivacyfoundation.org%22%2C%22
access_token%22%3A%221blahblahblahblah7%7Clb_lC8tSxxxxUVln-UQOxxxxx
sig%22xxxxblahblahblah91%22%7D"
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0"

 

A “bad” person would grab this access token and put it in a Graph API call like this.

https://graph.facebook.com/8xxxxxxx?access_token=1xxxxxxxxxx6|0xxxxxxxxxxxxxxxxxd1-8xxxxxxx|lb_lxxxxxxxxxxxxxxxxk

That’s a pretty simply graph API call, others are available via Facebook’s documentation

 

With server whitelisting turned off,  we can make a call the the facebook graph api to access data from the user in question and it works…

Graph API Access WITHOUT Server Whitelisting. Profile data is returned

Graph API Access WITHOUT Server Whitelisting. Profile data is returned

 

With server whitelisting turned on, the Graph API call fails to return anything.

Access with server whitelisting returns nothing

Access with server whitelisting returns nothing

 

“Ah, but anyone can spoof the IP address”

That’s true, but Facebook would then attempt to send the request back to the specified (spoofed) IP address and fail because it’s not the attackers IP address.

 

“Ah, but a really clever hacker could spoof the IP address and make it work”

True.  it’s possible (I guess), but the return on investment of such a hack doesn’t seem worthwhile, so I’d guess it’s sufficiently rare not to worry about.

 

If you’re interested in learning more about Facebook application security, I recommend the following links: