Within Facebook.com and Facebook mobile there is the option to search using advanced queries http://search.fb.com/. With this, it will be possible to determine whether data scoped behind a privacy level, e.g. “Friends Only” to be made available to public. – disclosing existence of an object.

The idea was originally taken from The Web Application Hacker’s Handbook Chapter 11 Example 10: Abusing a Search Function.

The “count” field allows one to determine whether a document/data exists or not.

Document doesn’t exist :: count = 0
Document exists :: count > 0

Two examples out of many that are possible (shown later in the POC section)

  • Find out if any user uses an application (even if “only me” set)
  • Dig for particular phrases/urls shared on a user’s timeline (even if timeline privacy is set to “only friends”)

With enough work put in it will be possible to build a search tool that abuses this functionality and gets around privacy settings.

POC

Concept 1: Find out if any user uses an application (even if “only me” set)

Given an application set to “Only Me” – Instagram by changing “App visibility and post audience” in https://www.facebook.com/settings?tab=applications

The following query at facebook.com gives no results

https://www.facebook.com/search/userABC/apps-used/str/Instagram/apps-named/intersect

However, if one were to take the same query in GraphQL

Response

{
"intersect(apps-used(userABC),apps-named(Instagram))": {
"url": https://www.facebook.com/search/userA/apps-used/str/Instagram/apps-named/intersect,
"results": {
"count": 4,
"nodes": [
]
}
}
}

To be clear, I am not worried about whether the count is accurate, the fact that it gives a count > 0 is definite that user userABC uses an app named Instagram.

Compared to “Instapotato”

Which gives 0 results

{
"intersect(apps-used(userABC),apps-named(Instapotato))": {
"url": https://www.facebook.com/search/userABC/apps-used/str/Instapotato/apps-named/intersect,
"results": {
"count": 0,
"nodes": [
]
}
}
}

This query works for any user in Facebook. How I believe this works,

apps-used(userABC) will return all apps userABC used, since the session user doesn’t have all privacy granted to see all the apps, some apps are filtered. However, the overall count is not. So simple set theory using the intersect function (given this function does obey the formality https://en.wikipedia.org/wiki/Intersection_(set_theory)) will guarantee that the result of the intersection is the app named “Instagram” used by userABC.

Concept 2: Dig for particular phrases/urls shared on a user’s timeline (even if timeline privacy is set to “only friends”)

Given the post https://www.facebook.com/userA/posts/1234567890 which is only scoped to friends

message field: hellofriendmov
link shared: http://www.apple.com/music/

Let user userDEF determine if the phrase “hellofriendmov” is in userABC timeline. (UserABC and UserDEF are not friends)

{
"intersect(stories(timeline(userABC)),stories-keyword(hellofriendmov))": {
"url": https://www.facebook.com/search/str/hellofriendmov/stories-keyword/userABC/timeline/stories/intersect,
"results": {
"count": 2,
"nodes": [
]
}
}
}

As opposed to a phrase which doesn’t appear “onesandzer0es”

{
"intersect(stories(timeline(userABC)),stories-keyword(onesandzer0es))": {
"url": https://www.facebook.com/search/str/onesandzer0es/stories-keyword/userABC/timeline/stories/intersect,
"results": {
"count": 0,
"nodes": [
]
}
}
}

Let’s try to find the same result via the link shared “http://www.apple.com/music/” Remember that the link isn’t shared in the body.

Using this intersection: intersect(stories(timeline(userABC)),stories-share-link(http://www.apple.com/music/))

{
"intersect(stories(timeline(userABC)),stories-share-link(http://www.apple.com/music/))": {
"url": https://www.facebook.com/search/str/http%253A%252F%252Fwww.apple.com%252Fmusic%252F/stories-share-link/userABC/timeline/stories/intersect,
"results": {
"count": 2,
"nodes": [
]
}
}
}

As opposed to “http://www.apple.com/taylorswiftnomusic/”

{
"intersect(stories(timeline(userABC)),stories-share-link(http://www.apple.com/taylorswiftnomusic/))": {
"url": https://www.facebook.com/search/str/http%253A%252F%252Fwww.apple.com%252Ftaylorswiftnomusic%252F/stories-share-link/userABC/timeline/stories/intersect,
"results": {
"count": 0,
"nodes": [
]
}
}
}

There are many other queries (e.g. search closed groups for documents) that can be made but these should suffice to prove that privacy is broken here.

Note

The lowest privacy of objects (apart from the “App visibility and post audience”) seem to be handled well. So a post with “Only me” doesn’t show a count, it actually shows null. In addition a group that has privacy set to “secret” shows null for queries.

Meaning going back to the set theory for intersection, as long as the two sets can be viewed, the intersection will occur. An intersect of null and an object will obviously return an error. So from my outside view of this, sets which include privacy levels above “Only me” aren’t being filtered properly. At the very least, the count field should return null or 0 for these since even though the accuracy of the number of objects isn’t guaranteed, the accuracy of existence of a post is indeed on point.

Timeline

Jun 30, 2015 12:45pm – Report Sent
Jul 1, 2015 4:42pm – Escalation by Facebook
Jul 2, 2015 10:22am – Patched by Facebook
Jul 2, 2015 8:14pm – Bounty Awarded of $5000 by Facebook

Facebook released Moments app momentsapp.com which allows syncing between friends. In particular, Facebook Code Blog talked about how synced objects might be stored

"notification" : {
"type": "string",
"senderUUID": "string",
"recipientUUID": "string",
"momentUUID": "string",
"photoUUIDs": "string", // JSON encoded array of photoUUIDs
...
},

However it seems by taking advantage of a GraphQL mutation any user who has been shared the photoUUID can rewrite the photo.

Prerequisite: Two users have installed the Moments App.

  1. So within User A Moments application there should be the photo synced
  2. User A has just synced a photo with user B
  3. This synced photo has an associated photoUUID as well as an object ID (let’s call this object ID 12345).
  4. Given the UUID which can be viewed by user B and the “attacking” user B access token
  5. Execute a GraphQL mutation setting a new photo in the payload and changing the UUID to the previous obtained UUID
  6. The response will have this UUID and the photo that was at 12345, changed to the new photo (That is the photo object will be rewritten)

References

Timeline

Jun 15, 2015 7:49pm – Report Sent
Jun 18, 2015 8:23pm – Escalation by Facebook
Jun 19, 2015 11:51pm – Patched by Facebook
Jun 26, 2015 5:28pm – Bounty Awarded of $3000 by Facebook

In Facebook 2.0, user_friends (me/friends) has only been limited to those friends who are using the application.

In addition finding a user’s friends who is not related to session user should not be possible. Finally given that two users’ friend lists are set to “Only me” privacy it should be possible for a third user to see whether these users are friends.

Using a Graph API call it is possible to do all of the above bypassing the privacy settings set here.

This is quite similar to Determine if any two users are friends without user_friends permission

The only difference is that I am executing an HTTP DELETE instead of an HTTP GET and using the error response as the indicator.

Timeline

Jun 9, 2015 10:49pm – Report Sent
Jun 10, 2015 9:22am – Escalation by Facebook
Jun 15, 2015 7:32pm – Patched by Facebook
Jun 22, 2015 12:42am – Bounty Awarded of $1250 by Facebook

According to Facebook, a scrapbook be used to collect photos of family members not using the site such as pets and babies. It is stated at https://www.facebook.com/help/1530275617253660

“Your child’s scrapbook doesn’t have its own privacy setting, but the individual photos do. When people visit a scrapbook, they’ll only see a photo if they’re included in that photo’s audience. The profile and cover photo of your child’s scrapbook are visible to anyone who can see at least one photo in the scrapbook.”

However this seems to indirectly affect a privacy setting in place at “Family and Relationships” in the about section of a user’s profile, which can be set to “Only Me” yet still be accessible via the API for other users.

  1. As userA add a family member https://www.facebook.com/userA/about?section=all_relationships without creating a scrapbook

    This user has added the pet “Yoda”

    The current privacy level set so far is “Anyone tagged”

  2. Access the user family info from user userB

    “No family members to show” will be displayed for userB

  3. Access via GraphQL userA as userB using the family_non_user_members edge

    Response

    {
    "userA": {
    "family_non_user_members": {
    "nodes": [
    ]
    }
    }
    }

  4. As user userA create a scrapbook for “Yoda”

    Seeing that https://www.facebook.com/help/1530275617253660 states the scrapbook doesn’t have a privacy setting, this suggests the scrapbook is “public”.

    Note that the privacy for “Yoda” is still set to “Only me” for userA

  5. Access via GraphQL userA as userB using the family_non_user_members edge again

    {
    "userA": {
    "family_non_user_members": {
    "nodes": [
    {
    "id": "ScrapbookID",
    "url": "https://www.facebook.com/media/set/",
    "name": "Yoda"
    }
    ]
    }
    }
    }

So either the privacy description needs to be changed or this information should not be accessed, from the family_non_user_members edge, for other users if the privacy scope is “Only me”

Additional smaller issues with the scrapbook feature

  • deleting the scrapbook removes the family member (when it should leave the family member in the about section)
  • tagging a family member in the API (HTTP POST /photo-id/tags?tag_uid=UID) seems to be allowed for third party applications with no option to remove the tag via HTTP DELETE /photo-id/tags?tag_uid=UID or removing the tag from facebook.com. This leaves the photo in the scrapbook “permanently” until the user deletes the scrapbook and starts over

Timeline

Jun 7, 2015 9:42pm – Report Sent
Jun 12, 2015 10:40am – Escalation by Facebook
Jun 30, 2015 12:22am – Patched by Facebook
Jul 2, 2015 7:39pm – Bounty Awarded of $2500 by Facebook

Initially this is a different way to recreate the bug found by Mohamed A. Baset around March 2014 https://www.youtube.com/watch?v=zepq4U-ahoU https://twitter.com/symbiansymoh/status/441364355448176640. The final decision however was that behavior does not pose a significant privacy/security risk to qualify for a payout. Nevertheless I decided it was a good idea to share to possibility of recreating old bugs.

In summary he was able to use the mobile (m.facebook.com) endpoint to change a status object to a added_photos object without changing the edit history.

The risk that was associated with this I believe, was that any user who commented/liked the owner’s post will not be notified of this change and from the current timeframe be seen to be associated with interacting with a photo they didn’t initially see.

The fix in place that I’ve seen is that using the link

https://m.facebook.com/photos/upload/?story_id=POST_ID&upload_source=timeline_story

Will result in the post showing the edit history.

Using an API endpoint I can recreate this bug leaving no edit history.

Proof of Concept

Given a whitehat account

Two posts were created

A “I love this life.” https://www.facebook.com/permalink.php?story_fbid=1379036662420184&id=100009415895135

B “I hate this life.” https://www.facebook.com/permalink.php?story_fbid=1379036692420181&id=100009415895135

Patched Method using A

a. Using the patched bug link for post ID 1379036662420184

https://m.facebook.com/photos/upload/?story_id=1379036662420184&upload_source=timeline_story

b. Choose a file and click upload.

The photo will be uploaded and the user will be redirected to

https://m.facebook.com/story.php?story_fbid=1379036662420184&id=100009415895135&_rdr

And the edit history is logged https://m.facebook.com/edits/?cid=1379036662420184

Graph API Method using B

a. Obtain a Facebook Native App user access_token for the user

This could have also been done by decompiling a Facebook app, getting the client and creating signature. I prefer the API login method.

b. Upload the photo via the /me/photos endpoint unpublished (published=false)

https://graph.facebook.com/me/photos?method=post&published=false&url=http%3A%2F%2F36.media.tumblr.com%2Fd0ab3ea70a8f7a1b705afcf265ee9c5a%2Ftumblr_n78ql57fcO1sk9t7ko1_500.jpg&access_token=...

c. Grab the ID from the response

{
"id": "1379039205753263"
}

d. Target the POST ID 1379036692420181 as 100009415895135_1379036692420181 (B) via publishing (published=1) the unpublished photo ID

https://graph.facebook.com/1379039205753263?method=post&target_post=100009415895135_1379036692420181&published=1&access_token=...

The response is true

e. Check the edits https://m.facebook.com/edits/?cid=1379036692420181

None are shown

f. Check the activity log

Notice that the timestamp isn’t set for when the photo was added but when the user initially created the post

Thus, I believe I have successfully found a workaround to the patch placed in m.facebook.com for the bug reported in March 2014.

The fix here will be the same as the original report, ensure that edit history is shown when any new object is added.

Timeline

Mar 22, 2015 8:21pm – Report Sent
Mar 24, 2015 5:42pm – Escalation by Facebook
May 19, 2015 8:37pm – Patched by Facebook
May 22, 2015 5:53pm – Facebook made the decision that this behavior does not pose a significant privacy/security risk to qualify for a payout.

It is possible to use an AJAX call to update the album of any user by using the idea that any photo in the album will have already been approved and thus pass the update.php call.

In Facebook albums there is a feature that allows either the approval/dismissal of photos added to albums from applications that did not create that album. This process is done via the facebook.com/ajax/photos/sets/pending/update.php call using the album_id and photo_fbids[0] parameters

The timestamp will have changed to reflect the album has been edited and the photo has been moved to the end. In albums that were untouched for a period of time, an “unauthorised” edit using the method will result in a story being created on the target user’s timeline.

Timeline

May 17, 2015 9:01pm – Report Sent
May 19, 2015 2:38pm – Escalation by Facebook
Jun 11, 2015 5:25pm – Patched by Facebook
Jun 15, 2015 10:36am – Bounty Awarded of $500 by Facebook

When using Facebook Messenger, it is possible to enable synced contacts from a user’s phone.

These operations are normally restricted to Facebook native whitelisted applications.

However there seems to be a check missing in one of the edges, specifically, an HTTP POST /me/contacts when using a third application.

Also, no additional permissions are needed other than public_profile to add a contact. So any 3rd application can in fact use this bug to the session user’s contact list.

HTTP POST https://graph.facebook.com/me/contacts?profile_id=13608786

The expected behaviour will be that

  • Third applications shouldn’t be allowed to execute this call
  • If they are, it shouldn’t be possible with just basic public_profile permissions

Timeline

May 17, 2015 4:15pm – Report Sent
May 19, 2015 6:37pm – Escalation by Facebook
May 21, 2015 11:50pm – Patched by Facebook
May 22, 2015 5:47pm – Bounty Awarded of $500 by Facebook

In Facebook 2.0, user_friends (me/friends) has only been limited to those friends who are using the application.

In addition finding a user’s friends who is not related to session user should not be possible. Finally given that two users’ friend lists are set to “Only me” privacy it should be possible for a third user to see whether these users are friends.

Using a Graph API call it is possible to do all of the above bypassing the privacy settings set here.

Full link

https://graph.facebook.com/v2.0/13608786/friends/friend_id

Response

{
"data": [

],
"debug": {
"messages": [
{
"link": "https://developers.facebook.com/docs/apps/changelog#v2_0",
"message": "Only friends who installed this app are returned in API v2.0 and higher. total_count in summary represents the total number of friends, including those who haven't installed the app.",
"type": "info"
}
]
}
}

The data is empty however the debug error message gives a sign that these two users are friends.

Trying for two users who are not friends

https://graph.facebook.com/v2.0/13608786/friends/not_friend_id

{
"data": [

]
}

So, one has solid indicator to determine if any two users are friends under any of the following

  • non-app users
  • non-mutual friends
  • no user_friends permission

The correct behaviour should be to set the debug error message for both success and failure of two users being friends.

Timeline

May 18, 2015 4:25pm – Report Sent
May 20, 2015 4:06pm – Escalation by Facebook
May 21, 2015 11:26pm – Patched by Facebook
May 22, 2015 5:47pm – Bounty Awarded of $1250 by Facebook

This used to be a bug that qualified for Facebook Bounty. However they no longer award these bugs. I still thought it was good to show what it was about. Facebook doesn’t allow usernames in their API however there are still places that it can be used or accessed.

Given an account with user_videos access and a session user with at least one video it is possible to determine the username of that session user in 2.0+ applications

Using a video from the user

Video ID: 10101896785723137

Prepend with the userID: 13608786

Post ID:13608786_10101896785723137

Calling this post will result in the link field being shown as

"link": "https://www.facebook.com/philippeharewood/videos/10101896785723137/",

Notice the username is placed here and not the app scoped ID.

May 11 at 12:06pm

May 11, 2015 12:06pm – Reported to Facebook Developer Support
May 11, 2015 4:51pm – Bug Assigned
May 20, 2015 8:54am – Patched

According to https://www.facebook.com/help/155909647811305 it isn’t clear as to what an Ad Analyst can view in terms of viewing Payment Methods associated with the account.

As an Ad analyst viewing this page https://secure.facebook.com/ads/manage/funding.php?act=ACT_ID results in

Ad Manager Error – You do not have permission to view this page

However using the API it is possible to see the credit card information listed here.

Caveats

  • This only lists the last four numbers
  • It also doesn’t list the expiration date

Nevertheless this is information that probably shouldn’t be available to the analyst if all the analyst is supposed to do is view the reports.

Timeline

May 10, 2015 6:03pm – Report Sent
May 19, 2015 6:14pm – Escalation by Facebook
May 28, 2015 2:45am – Patched by Facebook
May 29, 2015 6:54pm – Bounty Awarded of $1000 by Facebook