Stephen Sclafani

Stealing Login Nonces

March 21st, 2017

The 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 session for the user. Since session cookies are interchangeable with this gave full access to the user’s Facebook account.

Overview of the Login Flow

When a user visits the Facebook endpoint is loaded in an iframe:

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

If the user is logged into a Facebook account the endpoint redirects to with a secret nonce:

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

If they choose to continue a POST request is made to the 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

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 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 subdomain could be used.

When a nonce is delivered via a query string parameter in the redirect URL ( rather than as a hash fragment (, 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 endpoint allowed a hash to be appended to the redirect URL it was possible to append a #!/path which would be redirected to after 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 links just a 302 redirect is used.

Through some Google searches I was able to find the Facebook endpoint 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:!

Unfortunately this didn’t work how I expected. The referrer my PoC was receiving was This was because every 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 ( in cross-origin requests. This would have been a game-over if not for the endpoint allowing any subdomain to be used in the redirect URL. Through a search on I was able to find the subdomain 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:!

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

1. The endpoint redirected to with a nonce for the user’s account:!/l.php?

Because the 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 endpoint:

Because the link is to a URL a 302 redirect was used to redirect the user to, preserving the referrer containing the nonce.

3. The endpoint automatically redirected the user to the URL in the redirect_uri parameter:

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

The cookies could be used to access the user’s account on as well as on

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 to create the 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

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:!/l.php?

In this URL the #!/l.php is appended to the endpoint URL. This worked because modern browsers preserve an appended hash through a 302 redirect, even across sites. A hash appended to gets appended to 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.


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