Create post on any Facebook page

I discovered a vulnerability in Facebook Creative Hub that allowed an attacker to create an invisible (unlisted) post on an arbitrary Facebook Page without having a role on that Page.

The issue was caused by a missing authorization check when Creative Hub generated preview posts through its sharing workflow. Facebook fixed the original vulnerability after my report, but I later found a second path that bypassed the initial fix.

Product Facebook Creative Hub
Issue Missing authorization / access-control validation
Impact Create an invisible post on an arbitrary Facebook Page
Reported November 6, 2020
Final fix November 20, 2020
Bounty $30,000
Invisible post created on a Facebook Page
Example of the resulting Facebook Page post.

Background

Facebook supports different types of posts. One of them is an invisible, or unlisted, post. Unlike a normal Page post, it does not appear on the Page timeline, but it still has its own post ID and direct URL.

Because the post is not visible on the normal Page timeline, a Page administrator may not discover it through the usual Page interface. The post can still be accessed directly and shared with other users.

Facebook share feature for an unlisted post
An unlisted post can still be shared using its direct URL.

Creative Hub and Preview Posts

Facebook Creative Hub allowed advertisers to create ad mockups and collaborate on them. To preview a mockup, Facebook generated an invisible post on the selected Page.

While testing this workflow, I intercepted the request responsible for saving the mockup and changed the page_id to the ID of another Page. The modified request was accepted and saved without an authorization error.

Modified Creative Hub request containing the target page ID
The Creative Hub request where the Page identifier could be modified.

There was an authorization check in the normal preview-generation flow: a user was expected to have an advertiser role on the selected Page before Facebook would generate the preview.

Facebook Creative Hub permission error
The expected permission error when the user does not have the required role.

The Missing Authorization Check

Creative Hub also had a sharing feature for ad previews. After clicking Share, the API returned a shareable preview URL in this format:

https://www.facebook.com/ads/previewer/__PREVIEW_KEY__
Facebook Creative Hub share interface
Creative Hub's shareable preview workflow.

The important difference was that the authorization check was missing when a preview post was generated through the share page.

By changing the page_id in the GraphQL request before saving the mockup, then obtaining the shareable preview URL, it was possible to make Facebook generate a preview post associated with an arbitrary Page.

The remaining step was to obtain the resulting post_id, which was exposed through the ad-preview endpoints. This completed the chain and produced an invisible post on the target Page without the Page owner's knowledge.

Proof of Concept

Bypassing the Initial Fix

Facebook fixed the original vulnerability after I reported it. During verification, however, I found another Creative Hub workflow that could still generate the preview without performing the required Page permission check.

The Send to Mobile functionality called the following endpoint:

AsyncRequest.post('/ads/previewer/notify_mobile/__PREVIEW_KEY__', {})

Calling this functionality caused Facebook to generate the preview again and send a notification to the mobile device. The preview-generation step in this path did not enforce the expected Page authorization check, which allowed the original issue to be reproduced through a different workflow.

Bypass Proof of Concept

Facebook bug bounty award for the Creative Hub vulnerability
Facebook awarded a $30,000 bounty for the vulnerability.

Disclosure Timeline

November 6, 2020 Initial vulnerability reported to Facebook.
November 6, 2020 Report triaged.
November 11, 2020 Initial vulnerability fixed.
November 12, 2020 Fix bypass reported.
November 12, 2020 Bypass triaged.
November 20, 2020 Bypass fixed.
December 16, 2020 $30,000 bounty awarded.