What is Detectify?

GraphQL abuse: Bypass account level permissions through parameter smuggling

March 14, 2018

In this guest blog, security researcher and Detectify Crowdsource hacker Jon Bottarini writes about the interesting bugs he discovered when he took a closer look at Facebook’s popular GraphQL.

Since its release in 2015, Facebook’s GraphQL has gained steady adoption from organizations large and small. In my recent bug hunting I’ve encountered a few instances where I run into GraphQL queries, and I’ve found a few situations where they have led to juicy bugs – especially in the realm of privilege escalation and information disclosure.

If you’re unfamiliar with GraphQL, here’s a quick refresher: In its most basic use case, GraphQL allows you to call specific fields on objects – but that’s just the beginning. A typical REST function allows you to pass a single set of arguments, but with GraphQL you can retrieve multiple arguments at once – it’s as if you performing multiple REST queries, at the same time, within just one request!

Let’s say for example you want to find a user’s name, their favorite post, and then pull all the data from their profile and settings. With REST, you would need four separate requests to grab the information you need. With GraphQL, you make a single request, and the structured data allows you to pull all the pieces of data you need – how convenient.

GraphQL

But how can we abuse it?

One vulnerability I found was the ability to bypass the account level permissions set within the application and call queries through GraphQL that are normally only allowed to be called by administrators. I call this “smuggling” queries but there is probably a much more technical explanation I could have come up with if I thought about it longer. It can sometimes be difficult from a developer standpoint to map the user level account permissions to be the same across the regular application and GraphQL – so if they aren’t mapped properly you can “smuggle” additional parameters within regular GraphQL endpoints to call data you shouldn’t have access to given your user account permissions, here’s what it looks like in real life:

In New Relic (I was given permission to talk about the bug here) they have a few user level permissions that can be configured by administrators, one of the permissions (admin) has the ability to view the license key for the account, which provides extra functionality such as the ability to write metrics to the account – in a restricted user account it’s not possible to view this license key – but that’s where smuggling GraphQL parameters comes into play.

A normal GraphQL query in New Relic looks like this (from the restricted user account):

POST /accounts/REMOVED/graphql HTTP/1.1


{
  currentUser {
    email
    currentAccount {
      name
      capabilities {
        name
      }
      apmSubscription: subscription(productLine: "apm") {
        productLine
      }
      infraSubscription: subscription(productLine: "infrastructure") {
        trialEligibility {
          state
        }
        trial {
          endTime
        }
      }
    }
  }
}

Response:

{"data":{"currentUser":{"email":"redacted@gmail.com","currentAccount":{"name":"This is the account name”,"infraSubscription":{"trialEligibility":{"state":false},"trial":null},"capabilities":[huge list of capabilities],"apmSubscription":{"productLine":"apm"}}}}}

This might look a bit jumbled, but if we translate the request in English:

“Within the current user tell me their email address, now within the current account tell me the account name and capabilities, trial eligibility, and trial states”

Now, what went wrong is that the application failed to identify if the user that was performing the above query was an admin (it didn’t) – and therefore, I could “smuggle” a value to return within the “currentAccount” section of the query. If I smuggled the licenseKey value within the “currentAccount” section, it would return the license key for the whole account – bypassing the entire user permission for the restricted account.

“Smuggled” licenseKey parameter request:

POST /accounts/REMOVED/graphql HTTP/1.1

{
  currentUser {
    email
    currentAccount {
      name
      licenseKey
      capabilities {
        name
      }
      apmSubscription: subscription(productLine: "apm") {
        productLine
      }
      infraSubscription: subscription(productLine: "infrastructure") {
        trialEligibility {
          state
        }
        trial {
          endTime
        }
      }
    }
  }
}

Response:

{"data":{"currentUser":{"email":"redacted@gmail.com","currentAccount":{"name":"This is the account name", "licenseKey":"95d24ccefada021a6REDACTED","infraSubscription":{"trialEligibility":{"state":false},"trial":null},"capabilities":[huge list of capabilities],”apmSubscription":{"productLine":"apm"}}}}}

Nice. So from a restricted user with no access to the account license key, we are able to smuggle the “licenseKey” parameter within our request and pull the key for the entire account.

I found this bug by comparing the differences between the GraphQL queries that were sent on the administrator account and then on the restricted user account to see if they were different, and if an administrators’ query would be accepted on a restricted user account.

Part 2: Using Burp filter to find the juicy stuff within GraphQL queries

There was something else that I wanted to test having to do with GraphQL, but I needed Burp Suite to help me out. In New Relic, you can view the account license key if you have administrator privileges. Since I had an administrator account and I knew the account license key, I copied the license key value belonging to the administrator’s account and then pasted it into the filter settings within a fresh Burp instance. I then logged into the restricted user account and created a fresh Burp project – I wanted to see if the license key was ever revealed to the restricted user by accident – and the Burp filter would help me find it.

Once I navigated around the site on the restricted user account, I copy and pasted the license key into the filter option within Burp and let it do its magic:

GraphQL Burp

After a few seconds, I got a match:

GraphQL Burp

So what this accomplished was that it demonstrated that the account license key was still able to be exposed to a restricted user through this GraphQL logging request. Here’s the final proof of concept photo I sent to New Relic:

GraphQL Proof of Concept

New Relic was able to quickly patch both of these bugs very easily – they made it impossible for a restricted user to smuggle the licenseKey parameter – doing so would result in a “null” response. They also removed the stray endpoint that was disclosing the licenseKey parameter. All in all, another great response from the New Relic team and another reason why I tend to focus most of my bug hunting time on their program.

(Special thanks to Tanner (@itscachemoney) for reviewing this article for clarity before it was published.)

About the author:

Jon Bottarini

Jon Bottarini (@jon_bottarini) is a hacker/bug bounty hunter who has reported security vulnerabilities to organizations like Apple, Yahoo!, New Relic, the Department of Defense, and many more. He currently works as a Technical Program Manager for HackerOne, where he helps companies and organizations from all around the world run successful bug bounty programs and help make the internet more secure.

Interested in joining Jon and other security researchers on Detectify Crowdsource? Read up on how to become a Crowdsource hacker and apply!