in

Pentest GraphQL applications

Recently, GraphQL is gaining more and more popularity, and along with it, interest from information security specialists is growing up. The technology is used by companies such as Facebook, Twitter, PayPal, Github and others, which means it’s time to figure out how to test such API. In this article, we will explain the principles of this query language and the directions of penetration testing of applications with GraphQL.

Why do you need to know GraphQL? This query language is actively developing and more and more companies are finding practical use for it. The popularity of this language is also growing within the framework of the Bug Bounty programs, interesting examples can be found here , here and here . A list of apps you can use to learn too.

It is better to use the GraphQL IDE to interact with different APIs:

We recommend the last IDE: Insomnia has a convenient and simple interface, there are many settings and auto-completion of query fields.

Before proceeding directly to the general methods of analyzing the security of applications with GraphQL, let’s recall the basic concepts.

What is GraphQL?

GraphQL is an API query language designed to provide a more efficient, powerful and flexible alternative to REST. It is based on declarative data selection, where the client can specify exactly what data he needs from the API. Instead of multiple API (REST) ​​endpoints, GraphQL provides a single endpoint that provides the requested data to the client.

Key differences between REST and GraphQL

Typically in a REST API, you need to get information from different endpoints. In GraphQL, to get the same data, you need to make one request specifying the data you want to get.

The REST API provides the information that the developer will put in the API, that is, if you need to get more or less information than the API suggests, then additional actions will be needed. Again, GraphQL provides exactly the information requested.
A useful addition is that GraphQL has a schema that describes how and what data a client can get.

Types of requests

There are 3 main types of queries in GraphQL:

  • Query
  • Mutation
  • Subscription

Query Query

queries are used to retrieve / read data in a schema.

An example of such a request:

query {
  allPersons {
    name
  }
}

In the request, we indicate that we want to get the names of all users. In addition to the name, we can specify other fields: age , id , posts , etc. To find out which fields we can get, we need to press Ctrl + Space. In this example, we pass a parameter with which the application will return the first two records:

query {
  allPersons(first: 2) {
    name
  }
}

Mutation

If the query type is needed to read data, then the mutation type is needed to write, delete and modify data in GraphQL.

An example of such request:

mutation {
  createPerson(name:"Bob", age: 37) {
    id
    name
    age
  }
}

In this request, we create a user named Bob and age 37 (these parameters are passed as arguments), in the attachment (curly braces) we indicate what data we want to receive from the server after creating the user. This is necessary in order to understand that the request was successful, as well as to get data that the server generates itself, such as id .

Subscription

Another type of query in GraphQL is subscription. It is needed to notify users about any changes that have occurred in the system. It works like: the client subscribes to some event, after which a connection is established with the server (usually via WebSocket), and when this event occurs, the server sends a notification to the client over the established connection.

Example:

subscription {
  newPerson {
    name
    age
    id
  }
}

When a new Person is created, the server will send the information to the client. Subscription queries are less common in schemas than query and mutation.

It is worth noting that all the capabilities for query, mutation and subscription are created and configured by the developer of a specific API.

Optional

In practice, developers often use alias and OperationName in queries for clarity.

Alias

GraphQL for queries provides an alias feature that can make it easier to understand what the client is requesting.

Suppose we have a query like this:

{
  Person(id: 123) {
    age
  }
}

which will display the username with id 123. Let this username be Vasya.

In order not to rack your brains next time about what this query will display, you can do this:

{
  Vasya: Person(id: 123) {
    age
  }
}

OperationName

In addition to alias, GraphQL uses OperationName:

query gettingAllPersons {
  allPersons {
    name
    age
  }
}

OperationName is needed to explain what exactly the request is doing.

Pentest

After we have dealt with the basics, we proceed directly to the pentest. How do you know if your application is using GraphQL? Here’s an example query that has a GraphQL query:

POST /simple/v1/cjp70ml3o9tpa0184rtqs8tmu/ HTTP/1.1
Host: api.graph.cool
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: */*
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: https://api.graph.cool/simple/v1/cjp70ml3o9tpa0184rtqs8tmu/
content-type: application/json
Origin: https://api.graph.cool
Content-Length: 139
Connection: close

{"operationName":null,"variables":{},"query":"{\n  __schema {\n    mutationType {\n      fields {\n        name\n      }\n    }\n  }\n}\n"}

Some parameters by which you can understand that this is GraphQL, and not something else:

  • the request body contains the words: __schema, fields, operationName, mutation, etc.;
  • there are many “\ n” characters in the request body. As practice shows, they can be removed to make it easier to read the request;
  • often the way to send a request to the server is: ⁄graphql

Excellent, found and identified. But where to insert the quotation mark, how do we know what we need to work with? Introspection comes to the rescue.

Introspection

GraphQL provides an introspection scheme, i.e. a diagram describing the data that we can get. Thanks to this, we can find out what requests exist, what arguments can / should be passed to them, and much more. Note that in some cases, developers deliberately do not allow introspection in their application. Nevertheless, the main majority still leaves this possibility.

Let’s take a look at some basic query examples.

Example 1. Receiving all kinds of requests

query {
  __schema {
    types {
      name
      fields {
        name
      }
    }
  }
}

We form a “query” query, indicate that we want to get data on __schema, and in it types, their names and fields. There are service variable names in GraphQL: __schema, __typename, __type.

In response, we will receive all types of requests, their names and fields that exist in the schema.

Example 2. Getting fields for a specific type of query (query, mutation, description)

query {
  __schema {
    queryType {
      fields {
        name
        args {
          name
        }
      }
    }
  }
}

The answer to this query will be all possible queries that we can execute to the schema to obtain data (query type), and possible / necessary arguments for them. For some queries, the argument (s) are required. If you execute such a request without specifying a required argument, the server should issue an error message that you must specify it. Instead of queryType, we can substitute mutationType and subscriptionType to get all possible requests for mutations and subscriptions, respectively.

Example 3. Getting information about a specific type of request

query {
  __type(name: "Person") {
    fields {
      name
    }
  }
}

Thanks to this request, we get all the fields for the Person type. Instead of Person, we can pass any other request names as an argument.

Now that we can understand the general structure of the application under test, let’s define what we are looking for.

Information disclosure

Most often, an application using GraphQL consists of many fields and types of queries, and as many know, the more complex and larger the application, the more difficult it is to customize and maintain its security. That is why, with careful introspection, you can find something interesting, for example: the names of users, their phone numbers and other critical data. Therefore, if you want to find something similar, we recommend checking all possible fields and arguments of the application. So, as part of a pentest in one of the applications, user data was found: name, phone number, date of birth, some card data, etc.

Example:

query {
  User(id: 1) {
    name
    birth
    phone
    email
    password
  }
}

By iterating over the id values, we can get information about other users (or maybe not, if everything is configured correctly).

Injections

Needless to say that almost everywhere where a large amount of data, there are also databases? And where there is a database, there can be SQL-injections, NoSQL-injections and other types of injections.

Example:

mutation {
  createPerson(name:"Vasya'--+") {
    name
  }
}

Here’s a rudimentary SQL injection on the query argument.

Authorization bypass
Let’s say we can create users:

mutation {
  createPerson(username:"Vasya", password: "Qwerty1") {
  }
}

Assuming that there is a certain isAdmin parameter in the handler on the server, we can send a request like this:

mutation {
  createPerson(username:"Vasya", password: "Qwerty1", isAdmin: True) {
  }
}

And make the user Vasya an administrator.

DoS

In addition to the declared convenience, GraphQL has its own security flaws.

Let’s consider an example:

query {
  Person {
    posts {
      author {
        posts {
          author {
            posts {
              author ...
            }
          }
        }
      }
    }
  }
}

As you can see, we have created a looped subquery. With a large number of such attachments, for example, 50 thousand, we can send a request, which will be processed by the server for a very long time or even “drop” it. Instead of processing valid requests, the server will be busy unpacking the giant nesting of the dummy request.

In addition to large nesting, queries themselves can be “heavy” – this is when one query has a lot of fields and internal attachments. Such a request can also cause difficulties in processing on the server.

Conclusion

So, we covered the basic principles of penetration testing for applications with GraphQL. We hope you learned something new and useful for yourself. If you are interested in this topic and want to study it deeper, then we recommend the following resources:

What do you think?

49 Points
Upvote Downvote
Red Hat Professional

Written by Admin

NewbieAvatar uploadFirst contentFirst commentPublishing content 3 times

Comments

Leave a Reply

Loading…

0

How our accounts are snatched away via the npm

TorKill – hacking onion sites