Three Facebook Bugs Leading to Account Takeover ($126000 USD)
tl;dr - The Oauth endpoint parses URL paramters redirect_uri
and redirect_uri[0
(note the missing ]
) as pointing to the same variable. Allowing the second to overwrite the first. The front-end however sees them as two distinct keys and so redirects the oauth token to the redirect_uri
while the endpoint validates that the other value points to a whitelisted location
- Facebook validates attempts to register to receive cross-origin messages without an origin by checking that the registered app owns the url. The problem is that actions such as the Oauth dialog allow you to specify any app_id so you can receive messages intended for other origins. Enabling capture of first-party oauth tokens.
- Version information can be provided by the attacker and is simply prepended to the requested endpoint. Without validation an attacker can cause requests to go to unexpected endpoints like the graphql api.
Three vulnerabilities each receiving a $42,000 bounty and all dealing with message passing between Facebook games and apps hosted in an iframes on apps.facebook.com
and controlled by attackers and the parent Facebook frame. The child frames use postMessage(...)
to have the parent frame perform some action on behalf of the iframe.
Bug 1 - HTTP Parameter Pollution
One of the available actions is a jsonrpc
action to call the showDialog
method. Used to show a oauth login dialog to the end-user, and once the application has been authorized pass an access token back to the originating iframe. The parameters of this jsonrpc call are attacker controlled, and used to provide the various oauth parameters: redirect_uri
and APP_ID
are the two that matter here.
In the postMessage call, you also provide an origin
parameter. This value is used as the targetOrigin when the parent frame responds.
Under normal circumstances you provide your applications APP_ID
and origin
and Facebook will craft an oauth request with the redirect_uri set to https://www.facebook.com/dialog/return/arbiter#origin=...
. This is so it can catch the oauth reply and proxy it back to the iframe. This also means that the APP_ID
must whitelist that redirect_uri with the origin domain, and the response containing the token Facebook captures will be sent in a message targeting specifically that domain.
The attack comes down to how the parameters are processed. It’ll simply append all the parameters provided along with those it generated (the redirect_uri
) to the oauth URL. Not an issue on its own, but the bug is in the difference between redirect_uri=...
and redirect_uri[0=...
(notice the missing ]
). In the JSON RPC call, these are two distinct keys, however to the oauth server both redirect_uri=
and redirect_uri[0=
get parsed as the the same key, redirect_uri
. This enables an attacker to provide their own redirect uri value.
The attack here is to abuse a parameter pollution bug to create a desync between what redirect_uri
is being used. this allows the attacker to use the APP_ID
belonging to Instagram, and replace the generated redirect_uri
with a valid Instagram redirect. When the server sees the request it sees the Instagram APP_ID
and redirect_uri
and thinks everything is okay. Replies in the browser okaying the redirect, in the browser it sees original redirect_uri
as the valid one and redirects there with the token, providing the attacker Instagram’s token.
This is a great example of how subtle some of the bugs that deal with translating requests from one application to another can be.
Bug 2 - Undefined Origin
From the last bug we saw the https://www.facebook.com/dialog/return/arbiter#origin=...
url being used. That origin=
part in the fragment being the targetOrigin
used the response message. When it is missing from the url, it will check if a global origin was registered on the page and use that instead.
In order to register the global origin it will validate that the registered APP_ID
has verified that it owns the origin
provided before allowing it to receive messages on its behalf. This is to prevent an application from registering to receive messages targeting instagram.com
for example.
There are two issues that work together here:
- Some first-party applications have whitelisted the arbiter with no origin as a valid redirect_uri
- Even though the
APP_ID
andorigin
are checked when registering the global origin, the oauth request is not required to use the sameAPP_ID
.
An attacker could register a global origin their application owns, the trigger an oauth dialog to a first-party application that accepts the blank arbiter to receive the token.
Bug 3 - Versions can’t be harmful
Part of generating the end URI is the PlatformDialogClient.getURI
function. This will prepend the API version to the /dialog/oauth
endpoint. The version
can be passed in as part of the parameters in the original message, and is not validated in any way. This means you can trick the oauth dialog into making a request to other endpoints on the app.facebook.com
domain. The example used is to send a request to api/graphql/?...
allowing an attacker to trigger graphql requests.