Facebook has a GraphQL endpoint for viewing a backlog of ads clicked by a user. These ads can appear both on Facebook as well as Instagram in the form of sponsored ads or regular ads on the sidebar. Ads can be targeted based on audiences chosen in a campaign. It was possible for anyone to view the creator of an ad, the amount spent on all campaigns, the audiences, and the credit cards attached to the account.

Introduction

Facebook doesn’t have a public GraphQL schema or documentation available. So the following will be a demonstration of how it looked using collected assets, simplification of the endpoint and some educated guesses.

A demo GraphQL server can be created to follow along using the tutorial from Apollo’s React + GraphQL Tutorial — The Server Part 2. References from Graphql.org queries http://graphql.org/learn/queries/ and resolvers http://graphql.org/learn/execution/#root-fields-resolvers are used here as well.

With Facebook, generally, to query an object, the node root query can be used to obtain information about any ID, the user has permission to view. Public entities like Pages can be viewed whereas private entities like an Ad Account are scoped to within roles.

There are also other root queries, for example, nodes which can accept multiple IDs and return multiple objects in one call. Given a root query, default listings of objects and fields are shown.

Recon

With the endpoint found using custom recon methodology (recon here being used lightly to refer to monitoring assets) based on Shubs (@infosec_au) and Naffy (@nnwakelam) talk described in Scrutiny on the Bug Bounty, the next step was to find out what the data would load. Since I already collected and organized assets on Facebook, specifically GraphQL, using Shubs x Naffy ideas it was possible to get through this step without too much guessing.

Facebook uses an Invariant Detector (IVD), a defense-in-depth system to check for strange queries in authorization rules, which occasionally also catches bounty hunters, so throwing in generic wordlists to test for possible fields in the endpoint might not be a great idea.

We also want to keep our error count down in case we end up in any logs since the feature is still testing.

Luckily, Facebook allows some introspection in GraphQL. This means we should be able to figure out the type of data we are working with and use that to make some inferences.

Now that we know the type we can look through our assets and past queries to see if it will allow those fields.

Proof of Concept

Essentially to make this work, all that was needed was for the user to click an ad and watch the response to the backlog of clicked ads. A Python script polling the GraphQL call was used and demonstrated in a video to Facebook Security, the following are screenshots with redacted information.

Personal Facebook user account for the ad creator

Facebook Ad Account

Payment Method used (Mastercard/Visa/Paypal)

Last four digits and expiration date of credit cards

Total amount spent on ads in account

Facebook

Instagram

Since viewing the Ad Account was allowed, this meant queries to other campaigns can be seen as well as any other pixels, statistics, and audiences targeted. That is, clicking one ad, leaked all other ads from that account and ways that account was used to target audiences.

Impact

Although the credit card information is not targeted, it’s that case that ads will have a valid payment method attached, which makes this potentially reveal more valid cards than a targeted attack. Additionally, Facebook does not want to de-anonymize ads creators, which increases the impact of this bug.

Timeline