I discovered a vulnerability in Facebook Lead Ads that allowed me to create an arbitrary form capable of sending a POST request containing Facebook's anti-CSRF token to Facebook endpoints or external hosts.
The issue was similar in concept to a CSRF bypass I had found previously, but this time the vulnerable functionality existed inside Facebook's Lead Ads Continued Flow feature.
| Product | Facebook Lead Ads |
|---|---|
| Feature | Continued Flow |
| Issue | CSRF protection bypass / insufficient server-side validation |
| Impact | Send authenticated POST requests with the victim's Facebook CSRF token |
| Bounty | $7,500 |
Facebook's CSRF Protection
Facebook used the fb_dtsg parameter as an anti-CSRF token for many state-changing requests.
For protected actions, Facebook expected the token to be included in the request and validated server-side. Without a valid token, the request should normally be rejected.
The important question was whether any Facebook functionality could be abused to generate and submit requests containing a valid fb_dtsg token on behalf of the user.
Lead Ads Continued Flow
I found the vulnerable functionality in Facebook's Continued Flow feature of Lead Ads.
A Continued Flow Lead Ad allowed the final step of the form to be completed on the advertiser's website. Facebook could collect the user's submitted data and send it to a destination URL using either a hash or a POST request.
The interesting part was Facebook's POST implementation: when Continued Flow submitted the request, Facebook automatically added the user's fb_dtsg token.
Turning the Feature Into a CSRF Primitive
If the destination of the Continued Flow request could be controlled, the feature effectively became a way to generate authenticated POST requests containing a valid Facebook CSRF token.
That meant an attacker-controlled Lead Ad form could potentially submit requests directly to Facebook endpoints while carrying the victim's own fb_dtsg.
Bypassing the Continued Flow Whitelist
There was one obstacle: according to Facebook's documentation, Continued Flow was only available to whitelisted users.
While reviewing how Lead Ad forms were created, I noticed that Facebook sent the form configuration as a JSON object to the form-creation endpoint.
I then found another endpoint that returned previously created Lead Ad forms as JSON. Comparing the returned structure revealed additional fields related to Continued Flow.
I manually added those fields to the normal form-builder JSON and changed their values.
The server accepted the modified structure and created a Continued Flow form even though my account was not whitelisted for the feature.
There was no effective server-side validation enforcing the whitelist restriction.
Example: Disabling Timeline Review
As a proof of concept, I used the issue against Facebook's Timeline Review setting.
Target endpoint:
https://facebook.com/ajax/settings/timeline/review.php
Request body:
tag_approval_enabled=0
The resulting destination could be constructed as:
https://facebook.com/ajax/settings/timeline/review.php?tag_approval_enabled=0&__a=1
I tested the resulting Lead Ad using Facebook's own Lead Ads testing tool, and the request was successfully executed.
Proof of Concept
Another Interesting Case
During the research, I also tested what happened when the name of a custom Lead Ads field itself was set to fb_dtsg.
fb_dtsg as its name.Resolution
Facebook acknowledged and fixed the primary issue, and later fixed the additional whitelist-related issue as well.
Disclosure Timeline
| March 29, 2016 | Initial vulnerability reported. |
|---|---|
| April 6, 2016 | Facebook requested additional information. |
| April 6, 2016 | Additional technical details provided. |
| April 7, 2016 | Issue acknowledged by Facebook's security team. |
| April 7, 2016 | Additional fb_dtsg field behavior reported. |
| April 12, 2016 | Primary vulnerability fixed. |
| April 13, 2016 | $7,500 bounty awarded. |
| April 18, 2016 | Additional details about the whitelist bypass provided. |
| May 6, 2016 | Second issue fixed. |