tl;dr - The Oauth endpoint parses URL paramters
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:
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
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[0=... (notice the missing
]). In the JSON RPC call, these are two distinct keys, however to the oauth server both
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
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
originare checked when registering the global origin, the oauth request is not required to use the same
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.