Three Facebook Bugs Leading to Account Takeover ($126000 USD)

We discussed this vulnerability during Episode 79 on 06 September 2021

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 and origin are checked when registering the global origin, the oauth request is not required to use the same APP_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.