A misconfigured endpoint allowed legacy REST API calls to be made on behalf of any Facebook user using only their user ID, which could be obtained from their profile or through the Graph API. Through REST API calls it was possible to view a user’s private messages, view their private notes and drafts, view their primary email address, update their status, post links to their timeline, post as them to their friends’ or public timelines, comment as them, delete their comments, publish a note as them, edit or delete any of their notes, create a photo album for them, upload a photo for them, tag them in a photo, and like and unlike content for them. All of this could be done without any interaction on the part of the user.
An Interesting Request
When starting a pentest I like to browse the target site with Burp open to get a feel for how the site is structured and to see the requests that the site is making. While browsing Facebook’s mobile site touch.facebook.com the following request caught my attention:
The request was used to get your bookmarks. The request was interesting for three reasons: it was making an API call rather than a request to a dedicated endpoint for bookmarks; it was being made to a nonstandard API endpoint (i.e. not
graph.facebook.com); the call was not Graph API or FQL. Doing a Google search for
bookmarks.get turned up nothing. After some guessing I found that the method
notes.get could also be called which returned your notes. Through some more searching I found that the endpoint was using Facebook’s deprecated REST API.
The Facebook REST API
The request consists of the method being called, the application’s API key, a session key for a user, any parameters specific to the method, and a signature. The signature is a MD5 of all of the parameters and either the application’s secret, which is generated along with the API key when the application is registered with Facebook, or a session secret which is returned with a session key for a user. Web applications sign requests with their application secret. Requests signed with the application secret can make calls on behalf of users and to administrative methods. Desktop applications sign requests with a user’s session secret. Requests signed with a session secret are limited to making calls only for that user. This allows Desktop applications to make calls without exposing their application secret (which would have to be embedded in the application). An application obtains a session key for a user through an OAuth like authentication flow.
Making Calls on Behalf of Any User
From reading the documentation I knew that the actual REST API endpoint was
https://api.facebook.com/restserver.php which meant that the
https://touch.facebook.com/api/ endpoint had to be acting as a proxy. This raised the question: What Facebook application was it proxying requests as and what permissions did the application have?
So far I had only called read methods. I attempted to call the publishing method
Calling this method updated the status on the account that I was logged in to. The update was displayed as being made via the Facebook Mobile application:
This is an internal application used by the Facebook mobile website. Many internal Facebook applications are authorized and granted full permissions for every user. I was able to confirm that this was the case for the Facebook Mobile application by calling the methods
friends.getAppUsers showed that the application was authorized for every friend on the account that I was logged in to. Calling
fql.query allowed me to make a FQL query on the permissions table to lookup the permissions that the application had been granted.
That I was being authenticated with the REST server as the account that I was logged in to meant that the proxy had to be generating a session key from my session and passing it with each request. This should have limited my ability to make calls only for that account, however, I noticed that in the documentation for many of the methods a session key is optional if the method is being called by a Web application (i.e. the request is being signed with the application’s secret). For these methods a
uid parameter can be passed in place of a session key and set to the user ID of any user who has authorized the application and granted it the required permission for the method being called.
users.setStatus I had been able to find out what Facebook application the proxy was using, but more importantly I had been able to confirm that the proxy would pass any parameters to the REST server that I included in a request. The question now was: Was the proxy signing requests with the Facebook Mobile application secret or my session secret? And if the proxy was using the application secret, would the REST server accept the
uid parameter? Including the
uid parameter in a request would not stop the proxy from also passing a session key and there was the possibility that the REST server would reject the request if both were passed.
To test it I tried updating the status on a different account than the one I was logged in to by calling
users.setStatus with the
uid parameter set to user ID of that account. It worked. The status on the account whose user ID I passed was updated. Not only was the proxy signing requests with the application secret, but equally as important, when passed both a session key and the
uid parameter the REST server would prioritize the
The documentation for the REST API states that the use of the
uid parameter is limited to only those users who have authorized the application and granted it the required permission for the method being called. Since the Facebook Mobile application had been authorized and granted full permissions for every user, it was possible to use the
uid parameter to make calls on behalf of any user using any of methods that supported it.
The following methods can be called with the
||Returns all of a user’s messages.|
||Updates a user’s status.|
||Posts a link to a user’s timeline.|
||Publishes a post to a user’s timeline, friend’s timeline, page, group, or event.|
||Adds a comment to a post as a user.|
||Removes a user’s comment from a post.|
||Creates a new note for a user.|
||Edits a user’s note.|
||Deletes a user’s note.
This method is only supposed to delete notes that were created by the user through the application. However, in my tests, when called through the proxy it would delete any note.
||Creates a new photo album for a user.|
||Uploads a photo for a user.|
||Tags a user in a photo.|
||Likes content for a user.|
||Unlikes content for a user.|
Some methods that required a session key would return additional information when called through the proxy:
||Returns information on a user.
This method is only supposed to return the information on the user that is viewable to the calling user. However, when called through the proxy it would return the user’s primary email address regardless of the relationship between the user and the calling user.
||Returns the notes for a user.
This method is only supposed to return the notes for the user that are viewable to the calling user. However, when called through the proxy it would return all of the user’s notes, including their drafts.
In addition to the above user methods, the following administrative methods could be called through the proxy on behalf of the Facebook Mobile application:
||Gets the property values set for the application.|
||Sets the property values for the application.|
||Returns the demographic restrictions for the application.|
||Sets the demographic restrictions for the application.|
||Returns a list of the users who have been banned from the application.|
||Bans users from the application.|
||Unbans users from the application.|
||Revokes a user’s authorization of the application.|
||Revokes a extended permission for a user of the application.|
||Sends an email to a user as the application.|
I reported this issue to Facebook on April 23rd. A temporary fix was in place less than three hours after my report. A bounty of $20,000 was awarded by Facebook as part of their Bug Bounty Program.
April 23, 4:42pm – Initial report sent
April 23, 5:50pm – Request for clarification from Facebook
April 23, 6:08pm – Clarification sent
April 23, 6:49pm – Acknowledgment of issue by Facebook
April 23, 7:38pm – Notification of temporary fix by Facebook
April 23, 8:39pm – Confirmation of temporary fix sent
April 29, 11:03pm – Notification of permanent fix by Facebook
April 30, 12:58am – Confirmation of permanent fix sent
April 30, 8:35pm – Bounty awarded
Part 2 Preview
Update: Part 2 has been posted.
If you looked at the REST API Authentication guide and thought that there might be vulnerabilities there, you would have been correct. Both the Web and Desktop authentication flows were vulnerable to CSRF issues that led to full account takeover. These issues were less serious than the API endpoint issue as they required a user to load links while logged in to their account. However, the links could be embedded in a web page or anywhere where images can be embedded. I have embedded one as an image in this blog post. Click to display it. If you’re logged in to Facebook I’d have full access to your account (the issue has been fixed). In an actual attack loading the link would not have required a click.