More secure Facebook Canvas Part 2: More Account Takeovers ($98250 USD)

We discussed this vulnerability during Episode 125 on 07 March 2022

Two Facebook Canvas issues enabling an attacker application to get privileged first-party API keys by pretending to be Instagram or another first-party application.

The issues are reasonably easy, but they do require a bit of understanding regarding how Facebook Canvas applications work.Simplifying things quite a bit, communication with Facebook happens over postMessages from an iframe.

In a normal oauth flow the iframe would send a showDialog message, which would cause Facebook Canvas to go through a relatively normal oauth flow. One key thing is that the redirect_uri, because all communicate happens through a bit of a proxy (the arbiter) the URI would point to the facebook arbiter and the origin of the request would be added to the fragment (ex: https://www.facebook.com/dialog/return/arbiter#origin=https://example.com).

When the redirect happens, the Arbiter would send the code to the iframe restricting it to that origin. This means that an application might be able to tricker the arbiter into starting an oauth flow as a first party application but unless the https://...#origin=https://attacker.com was in the redirect whitelist they wouldn’t get the oauth token from the arbiter.

Race Condition

With that in mind, the origin that was added could also be registered through another postMessage (XdArbiter/register message type). When doing so, Facebook Canvas would first send a request to /platform/app_owned_url_check/ to ensure that the app_id actually owned the origin it was trying to register. Which is where we come across our first vulnerability.

First, one would try to register a new origin of fbconnect://success. This is an origin that all first-party applications whitelist. Then trigger the oauth dialog specifying a first-party application id.

While the oauth is happening, send another postMessage to register/change your origin, this time back to the attacker controlled origin. Now if this happened before the oauth flow finished, when the token comes back, the arbiter will send it to the registered origin, even though it is different from the origin that it used to make the request.

Fix Bypass

Part of the first for the race condition was to prevent an application from changing the application id away from the current id. This didn’t account for the encrypted_query_string parameter, which could be used to provide parameters that were not verified because they were encrypted. Combined with a parsing issue where proving a param[0] would cause param itself to be revoked/empty. An attacker could revoke the original app_id and redirect_uri parameters and have them parsed out of the encrypted_query_string parameter which wasn’t validated.