I discovered a vulnerability chain in Instagram and Facebook services that allowed me to de-anonymize Instagram users and retrieve personally identifiable information, including their name, email address, and phone number.
What started as a validation flaw in Instagram's Action Button configuration turned into a series of three related security issues. Facebook attempted to mitigate the original vulnerability twice, and each time I found a different way to restore the same impact.
The three reports were awarded $5,000 each, for a total of $15,000.
| Issue | Broken server-side validation, Linkshim bypass, referrer leakage, and PII disclosure |
|---|---|
| Impact | De-anonymize an Instagram user and retrieve their name, email address, or phone number |
| Total Bounty | $15,000 across the original issue and two bypasses |
Instagram Action Buttons
Instagram business accounts could add Action Buttons to their profile. These buttons connected an Instagram profile to services such as appointment booking, ticketing, and reservations.
One of the supported integrations was Appointments by Facebook.
The Original Vulnerability
When configuring an Action Button, Instagram first called a URL-validation endpoint:
/api/v1/business/instant_experience/ix_validate_url/
The request contained values such as:
app_id
url
This endpoint verified that the supplied URL belonged to the allowed scope of the selected application.
If validation succeeded, Instagram later sent a separate update request to:
/api/v1/accounts/update_business_info/
with fields such as:
ix_url
ix_app_id
is_call_to_action_enabled
The security problem was that the validation and update operations were completely separate.
Instagram validated the URL in ix_validate_url, but the backend responsible for update_business_info did not repeat that validation.
The server effectively trusted the client to only submit the URL that had previously passed validation.
Bypassing URL Validation
Because the update endpoint did not independently validate the URL, the validation response could be manipulated to make the Instagram client generate the final request, but that manipulation was not required for exploitation. The vulnerable update endpoint could also be called directly.
For example, I could intercept the validation request in Burp Suite, manipulate the validation result, and then allow Instagram to generate the final update request.
More importantly, the validation endpoint itself was not required at all. I could simply send the update request directly.
This allowed me to save:
- Facebook's legitimate App ID for the appointment integration
- My own attacker-controlled external URL
That combination should never have been accepted.
Why the Facebook App ID Mattered
When a user later clicked the Book button on the Instagram profile, Instagram checked the configured app_id.
Because the App ID belonged to Facebook's own appointment integration, Instagram treated the destination as trusted.
As a result, the normal Instagram Linkshim protection was not shown to the user.
Instead, Instagram navigated directly to my configured URL without showing Linkshim and included the Instagram user identifier in the navigation data.
Turning the Token Into User PII
Once the user reached my server, I extracted the Instagram-related user identifier and passed it to a server-side script.
That script requested Facebook's appointment endpoint:
https://m.facebook.com/services_vertical/book_appointment/
?page_id=PAGE_ID
&ig_ix=true
&user_id=USER_ID
The response contained structured data related to the Instagram user.
Among the fields exposed by the response were:
consumerNameconsumerEmailconsumerPhoneNumberexternalUserIDplatformpageID
This meant that a user who simply clicked the Book button could potentially be mapped to their real name, email address, or phone number.
Why the Report Was Initially Rejected
From their perspective, modifying the response from ix_validate_url in Burp looked like changing local application behavior rather than bypassing a security control.
The important distinction was that I was not relying on the manipulated validation response itself for the exploit.
I only manipulated that step to make the Instagram client generate the final update_business_info request.
The actual vulnerability was entirely server-side: the update endpoint accepted an unvalidated URL.
Once that was understood, Facebook accepted the issue.
First Fix
Facebook's first mitigation did not fix the vulnerable backend logic.
Instead, they removed or restricted Appointments by Facebook so that normal users could no longer select it while creating an Instagram Action Button.
From the user interface, the vulnerable integration was effectively gone.
But the backend still supported it.
First Bypass
I already knew the Facebook App ID used by the appointment integration.
So instead of selecting the integration through the UI, I sent the update_business_info request directly and supplied the original Facebook appointment App ID myself.
The backend still accepted it.
That recreated the same configuration and restored the original vulnerability.
The First Bypass Was Rejected Too
Facebook initially rejected this bypass as well.
The initial review assumed the mitigation was enforced server-side rather than only through the UI.
I sent additional proof-of-concept material demonstrating that the backend still accepted the Facebook App ID directly and that the vulnerable Action Button could still be created without using the UI.
Facebook eventually reproduced the bypass and accepted the report.
Second Fix: Server-Side URL Validation
After the first bypass, Facebook implemented the validation that should have existed from the beginning.
The update endpoint now verified that a URL associated with the Facebook appointment App ID belonged to an allowed Facebook domain.
My external domain could no longer be saved directly.
At this point, simply calling the update endpoint was no longer enough.
I needed a URL that:
- belonged to an allowed Facebook domain
- could eventually redirect the user toward infrastructure I controlled
- preserved the Instagram token during navigation
Looking at Referrer Policy
The token was present in the navigation URL, so browser referrer behavior became important.
A restrictive Referrer-Policy can prevent the full path and query string from being disclosed during cross-origin navigation.
I wrote a small script to scan Facebook subdomains looking for hosts that did not enforce the expected restrictive referrer policy.
Finding Facebook Workplace
The scan led me to:
fb.m.facebook.com
This was a trusted Facebook subdomain used by Facebook Workplace.
One Workplace language-selection endpoint accepted a destination through an n parameter.
A request looked conceptually like:
https://fb.m.facebook.com/language.php?n=FACEBOOK_URL
The destination was restricted to Facebook-controlled domains, which normally would have made the redirect harmless.
Preserving the Referrer
The important part was that this Workplace endpoint did not apply the referrer policy that would have removed the sensitive path and query string.
When the victim clicked one of the language options, the browser navigated to the URL supplied in n.
Because of the referrer behavior, the previous URL — including the information passed from Instagram — could continue through the next navigation step.
Chaining a Facebook Redirect
The next problem was escaping from the Facebook-only destination restriction.
I already had another Facebook-controlled redirect behavior from separate research.
I used that redirect as the next hop in the chain. I am intentionally not documenting that unrelated endpoint in detail here.
Conceptually, the navigation became:
Instagram
↓
fb.m.facebook.com/language.php?n=FACEBOOK_REDIRECT
↓
facebook.com/...
↓
fbcdn.net/...
The Facebook redirect accepted fbcdn.net as a trusted Facebook destination.
Because the intermediate pages did not apply a restrictive referrer policy, the sensitive path and query information survived the next navigation step.
Attacker-Controlled HTML on fbcdn.net
I had also found a way to host attacker-controlled HTML on a Facebook CDN origin.
That gave me executable content under fbcdn.net, which was already accepted as a trusted destination by the preceding Facebook redirect.
The HTML page read document.referrer.
Because the previous redirects had preserved the full referrer, the page could recover the information originally passed from Instagram.
The page then forwarded that value to my own server.
Restoring the Original PII Disclosure
Once my server received the Instagram user identifier again, the final step was exactly the same as in the original vulnerability.
My script submitted the identifier to:
https://m.facebook.com/services_vertical/book_appointment/
?page_id=PAGE_ID
&ig_ix=true
&user_id=USER_ID
The Facebook appointment response once again exposed the user's associated information.
The full second-bypass chain was therefore:
- The victim clicks the Instagram Book button.
- Instagram opens the allowed
fb.m.facebook.comWorkplace URL with the user identifier embedded in the URL. - When the user chooses a language, the browser sends that full URL as the referrer to the next Facebook-controlled hop.
- The Workplace language endpoint preserves the sensitive referrer.
- The victim selects a language and is redirected to another Facebook-controlled endpoint.
- That endpoint redirects to a trusted
fbcdn.netdestination. - Attacker-controlled HTML on
fbcdn.netreads the full referrer. - The HTML sends the recovered identifier to my server.
- My server queries the Facebook appointment endpoint.
- The Instagram user's PII is returned.
| Original Issue | Missing server-side URL validation in the Action Button update flow — $5,000 |
|---|---|
| First Bypass | Directly reusing the hidden/restricted Facebook appointment App ID — $5,000 |
| Second Bypass | Facebook-domain validation bypass through Workplace, referrer propagation, Facebook redirects, and fbcdn.net — $5,000 |
| Total | $15,000 |
Conclusion
The most interesting part of this research was how the vulnerability evolved after each mitigation.
The original bug was fundamentally a server-side validation inconsistency: Instagram validated the Action Button URL in one endpoint but trusted the client when the same data reached the update endpoint.
Facebook's first mitigation addressed the visible feature rather than the underlying trust boundary, so directly calling the backend restored the vulnerability.
The second mitigation correctly introduced server-side URL validation, but the broader Facebook ecosystem still provided enough primitives to rebuild the attack through trusted domains.
By combining a Workplace redirect flow, weak referrer handling, another Facebook-controlled redirect, trusted CDN infrastructure, and attacker-controlled content on that CDN, I was able to recover the Instagram identifier and reach the original PII-disclosure endpoint again.
For me, this research was a good demonstration of a recurring security lesson: fixing the immediate vulnerable path does not necessarily eliminate the exploit when equivalent trust relationships remain elsewhere in the system.
No comments