tl;dr I found a vulnerability in Facebook that allowed anyone to see the “strength” of all his friends’ friendships via a FQL query authenticated with an iOS app access token. The reporting experience has been smooth and not too slow, and the bounty generous.
Early this year Facebook started showing “last active” times above chats in the iOS app but not on the website (they now have rolled out it also in the website) and I started wondering if I could get that info also from my browser and maybe wrap it up in a extension or something (turns out visiting m.facebook.com
would have been enough, but emh…). So, I started Burp Proxy with SSL interception, trusted the Burp CA on my iPad and started inspecting the Facebook iOS app calls.
They are mainly FQL multiqueries, and one in particular caught my eye (queries
parameter URLDecoded for readability):
GET /method/fql.multiquery?sdk=ios&queries={"top_friends":"SELECT uid, online_presence, is_pushable, has_messenger, last_active FROM user WHERE uid in (SELECT uid2 FROM friend WHERE uid1=me() order by communication_rank desc LIMIT 15)","online":"SELECT uid, is_pushable, has_messenger FROM user WHERE online_presence ='active' AND uid IN (SELECT uid2 FROM friend WHERE uid1=me())","favorites":"SELECT uid, online_presence, is_pushable, has_messenger, last_active FROM user WHERE uid in (SELECT favorite_id FROM messaging_favorite WHERE uid=me())","favoriteRanking":"SELECT favorite_id, ordering FROM messaging_favorite WHERE uid=me()"}&sdk_version=2&access_token=REDACTED&format=json&locale=it_IT
These are a bunch of queries against the user
table based on JOINs on the messaging_favorite
and friend
tables. This friend
table is interesting: it holds all the friendships with their details, for example communication_rank
. By fiddling around a bit with it I guessed that it is a rank of the “strength” of uid1
’s friendship with uid2
, probably the thing that decides who’s shown in your chat sidebar even when he’s offline.
The docs tell us that “[the access token owner is] the only user that this table can be queried for, the friends of friends cannot be retrieved”. Hmm. Should we trust the docs? Turns out, NO! ;)
Authenticating with our iOS app access token we can issue queries like
SELECT uid2, communication_rank FROM friend WHERE uid1=1289695510 ORDER BY communication_rank DESC
for an arbitrary friend’s uid1
(the above is the one of my favorite guinea pig, Anna) instead of just for ours. That queries return output like
{ "uid2": "1234567890", "communication_rank": "2.4456558227539" },
{ "uid2": "4242424242", "communication_rank": "1.68115234375" },
{ "uid2": "1337133713", "communication_rank": "1.602783203125" },
...
that tells us with which users the target contacts most, maybe the most interesting and private bit of information after message logs. We don’t even need to be friends of that users!
Reporting and patching
The first time I reported the issue through their form it got dismissed, probably also because I didn’t explain it very well. After offering a real world example they confirmed and quickly patched it.
The report netted me a generous 4200$ bounty (delivered as a cool prepaid card, that is worth the withdrawal fee) and a mention on their thanks page (a great, great CV builder), plus some pleasant compliments.
Facebook offers a great example of how to run a Bug Bounty Program: assure the researcher that he’s being heard, offer him a direct contact (you get ticket-bound Reply-Tos) by skilled people, reward him generously (even if, believe me or not, this is the most optional point), publicly thank him and finally let him feel that his work is appreciated.
Vulnerability timeline
1 gen 2013 | Vulnerability discovered and bug report filed |
7 gen 2013 | Test accounts POC sent |
18 gen 2013 | First dismissing reply received |
19 gen 2013 | Better explanation and real-users POC sent |
28 gen 2013 | Vulnerability confirmed and acknowledged |
~ 30 gen 2013 | Vulnerability fixed |
1 feb 2013 | Bounty awarded |
18 mar 2013 | Bounty paid |
26 apr 2013 | This public disclosure |