Why you should not use GraphQL schema generators

Why you should not use GraphQL schema generators

It has been quite a while since GraphQL has been introduced by Facebook, lots of tools and frameworks has appeared and are being used in the wild now. In 2017 I made an overview of the technology from the security point of view in the post “ Looting GraphQL for Fun and Profit ” and some of the predictions turned out to be true. For instance, “resolvers may contain ACL-related flaws“. In this post I would like to show an example of such case in a popular GraphQL backend called graphcool .

Graphcool is a framework which “offers an opionated backend setup, including GraphQL database mapping, subscriptions & permission system“. Let’s do a quickstart and deploy our new GraphQL service. The process is quite straightforward:

 npm install -g graphcool
 graphcool init hello-world
 cd hello-world
 

Let’s have a look at types.graphql:

 # The following types define the data model of the example service
 # based on which the GraphQL API is generated
 
 type User @model {
   id: ID! @isUnique
   name: String
   dateOfBirth: DateTime
 
   # Uncomment below - you can declare relations between models like this
 
   # posts: [Post!]! @relation(name: "UserPosts")
 }
 

As per documentation :

Graphcool uses (a subset of) the GraphQL  Schema Definition Language  for data modelling. Your data model is written in your service’s .graphql-file, typically called types.graphql, which is the foundation for the actual database schema that Graphcool generates for you.

It means that graphcool generates an actual GraphQL schema from types.graphql, so it is not the final schema. Assuming that we want to protect some private data, let’s change the line dateOfBirth: DateTime with password: String. One cannot define security policies for particular fields in the schema itself, but it is possible to do so in graphcool.yml. The documentation says:

In general, permissions follow a whitelist approach:

  • no operation is permitted unless explicitely allowed
  • a permission cannot be nullified by other permissions

In order to restrict password field from being directly accessed, we have to change permissions policy in graphcool.yml the following way:

 permissions:
    # - operation: "*" disable default "allow all"
    - operation: User.create # allow creating new Users
    - operation: User.read   # allow to read only name field
      fields: [name]
 

Awesome, we are ready to deploy the GraphQL service:

Default web API is a GraphQL Playground which allows to view the schema and execute queries against the backend. Let’s create a new user Alice:

The permissions we have defined indeed work, it is not possible to read password field:

An attempt to read password results in an “insufficient permissions” error

At this point authorization looks solid, but do you remember that an actual schema was generated from an SDL model? Let’s explore it:

Take a look at the right panel. Graphcool automatically created additional fields with selectors: password_contains, password_not_contains, password_starts_with, etc. Apparently it is needed because GraphQL does not support anything like that out of the box.

What if we try to enumerate characters at the beginning of Alice’s password? It can’t be that bad, right?

password indeed does not start with an “a”

Bad luck! Query didn’t return any results, but wait… Where is permission error?

After submitting a correct first character which is “$”, we can see a permission error! This means that it is possible to recover the password’s value using a traditional error-based approach which is used in different kinds of injections, specifically in SQL injections. As a PoC Burp Intruder can be used:

As you can see the size of a response of a correct letter in the second position is larger than others. Thus, all characters can be recovered one by one. Shodan reveals numerous graphcool backends which are vulnerable against such attack.

As a conclusion, developers should manually examine the resulting GraphQL schemas because frameworks can inject unexpected fields and fail to authorize them properly. On a positive side, graphcool has recently introduced a new project prisma which has a new approach to authentication & authorization.

Alt text

Ваша приватность умирает красиво, но мы можем спасти её.

Присоединяйтесь к нам!

Арсений Реутов

блог о web-безопасности