A Primer for Testing the Security of GraphQL APIs

Whether you’re a penetration tester, security engineer, or bug bounty hunter, it can be incredibly helpful to know how to find vulnerabilities in a GraphQL API. This post will introduce you to GraphQL and its functionality from the perspective of someone performing a security assessment.

无论你是渗透测试人员、安全工程师还是 bug 赏金猎人,了解如何在 GraphQL API 中找到漏洞都是非常有帮助的。本文将从执行安全评估的角度向您介绍 GraphQL 及其功能。

The post will not focus on how to securely implement a GraphQL API, although you can extrapolate details that’ll help you in doing so. Additionally, although I will draw parallels to familiar topics like REST and SQL, other concepts may be new.

这篇文章不会关注如何安全地实现 GraphQL API,尽管您可以推断出有助于实现这一点的细节。此外,尽管我将把它与熟悉的主题(如 REST 和 SQL)进行对比,但其他概念可能是新的。

A Brief Introduction to GraphQL

GraphQL 简介

GraphQL is a specification that was developed by Facebook and later moved to the GraphQL Foundation. The high-level description from the GraphQL Foundation is:

GraphQL 是 Facebook 开发的一个规范,后来移植到 GraphQL 基金会。GraphQL 基金会的高级描述是:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

GraphQL 是用于 api 的查询语言,也是用于使用现有数据完成这些查询的运行时。提供了 API 中数据的完整且易于理解的描述,让客户能够精确地询问他们需要什么,仅此而已,让 API 随着时间的推移更容易发展,并支持强大的开发工具。

Here is our security-relevant, high-level overview of GraphQL:

下面是我们对 GraphQL 的安全相关的高级概述:

  • Instead of multiple HTTP requests, a single HTTP request can request all the resources that are needed.
  • 单个 HTTP 请求可以请求所有需要的资源,而不是多个 HTTP 请求。
  • The query language is heavily inspired by JSON.
  • 查询语言深受 JSON 的启发。
  • The responses returned from a GraphQL API are valid JSON and can be parsed as such.
  • 从 GraphQL API 返回的响应是有效的 JSON,因此可以进行解析。
  • GraphQL is strongly typed, which reduces the attack surface from user input. However, it’s still not foolproof.
  • GraphQL 是强类型的,这减少了用户输入的攻击面,但仍然不是万无一失的。
  • GraphQL is database agnostic and can even be backed by other APIs, including other GraphQL APIs.
  • GraphQL 与数据库无关,甚至可以由其他 api (包括其他 GraphQL api)支持。
  • GraphQL APIs can be used to retrieve data (query) and modify data (mutation).
  • GraphQL api 可用于检索数据(查询)和修改数据(变异)。

GraphQL is regarded by some as an alternative to REST. There are many implementations of GraphQL engines and clients. Because the reference implementation is written in JavaScript, much of the ecosystem is also written in JavaScript, although other languages are supported as well. Most GraphQL engines do the bare minimum required by the spec and aren’t a “batteries-included” solution in the way Python’s Django may be considered. Much is left up to the developer, sort of like Python’s Flask. And just like how there are a lot of third-party addons for Flask, there’s a similar ecosystem around GraphQL. The ecosystem has much more tooling support for JavaScript GraphQL engines; if you’re testing a GraphQL API not written in JavaScript, you will need to review much more hand-crafted custom code.

一些人认为 GraphQL 是 REST 的替代品。有许多 GraphQL 引擎和客户机的实现。因为引用实现是用 JavaScript 编写的,所以很多生态系统也是用 JavaScript 编写的,尽管其他语言也受到支持。大多数 GraphQL 引擎完成了规范要求的最低要求,并且不像 Python 的 Django 那样是“包含电池”的解决方案。很多东西留给了开发者,有点像 Python 的 Flask。就像 Flask 有很多第三方插件一样,围绕 GraphQL 也有一个类似的生态系统。生态系统对 JavaScript GraphQL 引擎有更多的工具支持; 如果您正在测试一个不是用 JavaScript 编写的 GraphQL API,那么您将需要查看更多手工定制的代码。

It’s a good idea to review the Introduction to GraphQL to create familiarity. Additionally, there are public GraphQL APIs like SpaceXLand where you can get the hang of how queries work. We’ll just scratch the surface enough to get you going here.

回顾一下 GraphQL 介绍,以便熟悉它,这是一个好主意。此外,还有像 SpaceXLand 这样的公共 GraphQL api,您可以在这些 api 中了解查询的工作方式。我们只是触及表面,足以让你到这里来。

Types

类型

As mentioned earlier, GraphQL uses static types. An object type is made up of fields along with types defining what is accepted by each field.

如前所述,GraphQL 使用静态类型。对象类型由字段和定义每个字段接受内容的类型组成。

Below is an example object type called Character:

下面是一个叫做字符的对象类型的例子:

type Character {
  name: String!
  appearsIn: [Episode!]!
}
  • The field name has a type of String, and the ! following it means that it is non-nullable. It is not an optional parameter and must be a non-zero value for the structure to be valid.
  • 字段名具有 String 类型,而!跟随它意味着它不可为空。它不是一个可选参数,并且必须是一个非零值才能使结构有效。
  • The appearsIn field has an array type [] that is non-nullable and is made up of objects with the Episode type, which is also non-nullable.
  • appearsIn 字段有一个数组类型[] ,它是不可为空的,并且由使用 Episode 类型的对象组成,后者也是不可为空的。
  • Episode is a type that was defined just like Character. In this case, it is used within another type.
  • Episode 是一个类型,它的定义类似于 Character。在这种情况下,它用在另一个类型中。

In the example above, String is considered a scalar type. Eventually a field will have to resolve actual data. Scalar types represent the actual data that will be resolved, such as a string or integer. Unlike other types, like Episode, these do not have subfields. There are built-in scalar types for the most common use-cases, although custom scalar types can also be created by developers. Pay close attention to any scalar type that is custom, since custom types are more likely to have issues.

在上面的示例中,String 被认为是一种标量类型。最终,字段将不得不解析实际数据。标量类型表示将被解析的实际数据,如字符串或整数。不像其他类型,比如 Episode,它们没有子字段。尽管开发人员也可以创建自定义标量类型,但对于最常见的用例,还是有内置标量类型。密切关注自定义的任何标量类型,因为自定义类型更有可能出现问题。

Feel free to also look into enumeration types as well, just so you know that they exist.

也可以随意查看枚举类型,这样您就知道它们的存在。

Operations Types

操作类型

There are three operation types, but the first two are the ones you’ll see most often:

有三种操作类型,但前两种是你最常见的:

  • Query – operations that fetch data (analogous to REST API’s GET request)
  • 获取数据的查询操作(类似于 restapi 的 GET 请求)
  • Mutation – operations that modify data (like a POST request)
  • 变异-修改数据的操作(如 POST 请求)
  • Subscription – operations that wait for the server to update the client with data, usually over WebSockets (PubSub)
  • 等待服务器用数据更新客户端的订阅操作,通常是通过 websocket (PubSub)

Even though query is an operation type, we also refer to all requests as queries no matter what the operation type is.

即使查询是一种操作类型,我们也将所有请求称为查询,不管操作类型是什么。

Queries

查询

All GraphQL queries must include fields that you want to be returned in the response. Fields are specific pieces of data within an object like an ID, name, or address. In the following example, we are using the users query to request the name of all users:

所有 GraphQL 查询必须包含您希望在响应中返回的字段。字段是对象中的特定数据片段,如 ID、名称或地址。在下面的例子中,我们使用用户查询来请求所有用户的名字:

Request

请求

{
  users {
    name
  }
}

Response

回应

{
  "data": {
     "users": [
      {
        "name": "Carmen"
      },
      {
        "name": "Nathan"
      }
     ]
  }
}

Some queries may also accept arguments. A query similar to the one above is the singular version user, which accepts the argument id among others. As a result, we get the name of the user with a particular ID:

有些查询还可能接受参数。与上面类似的一个查询是单一版本用户,它接受参数 id 和其他用户。因此,我们得到了一个特定 ID 的用户名:

{
  user(id: "1000") {
    name
  }
}

For mutations, you might see a field like add_user, where you can pass an argument to create a new user. In this case, we wanted to return the id of the newly created user after completion:

对于变异,您可能会看到像 add _ user 这样的字段,在这里您可以传递一个参数来创建新用户。在本例中,我们希望在完成后返回新创建用户的 id:

mutation {
  add_user(name: "Palo") {
    id
  }
}

Variables

变量

So far, we’ve been passing values as arguments directly in the query, but it’s also possible to set up a query in a way similar to a prepared statement in SQL. This method allows sending the values separately.

到目前为止,我们一直直接在查询中传递值作为参数,但是也可以以类似于 SQL 中的预处理语句的方式设置查询。此方法允许分别发送值。

Let’s take the previous example and pass the name “Palo” as a variable.

让我们以前面的例子为例,将名称“ Palo”作为变量传递。

Query

查询

mutation anything($name: String!){
  add_user(name: $name) {
    id
  }
}

Variables

变量

{"name": "Palo"}

The query and variables will be submitted to the GraphQL API in the same request, but are sent as separate parameters.

查询和变量将在同一个请求中提交给 GraphQL API,但是作为单独的参数发送。

Fragments

碎片

You will probably see queries that contain ... in them. Those are fragments. Fragments are used to simplify the reuse of a set of fields. You can define the set of fields and use them as a fragment throughout your queries. Although fragments are good to understand, queries can always be created without them.

您可能会看到查询中包含… … 。那些都是碎片。使用片段可以简化一组字段的重用。您可以定义字段集,并在整个查询中将它们作为片段使用。尽管片段很容易理解,但是查询总是可以在没有片段的情况下创建。

GraphQL API Interaction

相互作用

So how do we talk to a GraphQL API? Well, it happens over HTTP, and all interactions are done through POST requests against a single endpoint like https://example.com/graphql. What changes between requests is the query and variables parameters sent in the request. The endpoint remains the same; only the request body changes.

那么我们如何与 GraphQL API 对话呢?这是通过 HTTP 发生的,所有的交互都是通过 POST 请求来完成的,对象是一个单一的端点,比如 https://example.com/graphql。请求之间的更改是请求中发送的查询和变量参数。端点保持不变; 只有请求正文发生变化。

Here is an example of a GraphQL API HTTP request and response:

下面是一个 GraphQL API HTTP 请求和响应的例子:

Request

请求

POST /graphql HTTP/1.1
Host: example.com
...
Content-Type: application/json
Connection: close

{"query":"{\n  capsules {\n    id\n  }\n}\n","variables":null}

Response

回应

HTTP/1.1 200 OK
...
Content-Type: application/json

{"data":{"capsules":[{"id":"C105"},{"id":"C101"},{"id":"C109"},{"id":"C110"},{"id":"C106"},{"id":"C102"},{"id":"C205"},{"id":"C103"},{"id":"C201"},{"id":"C104"},{"id":"C111"},{"id":"C113"},{"id":"C108"},{"id":"C107"},{"id":"C112"},{"id":"C206"},{"id":"C203"},{"id":"C202"},{"id":"C204"}]}}

This is a pretty basic query, but as the queries and responses get larger it becomes a pain to work with manually. When it comes time to constructing queries, you are going to want to use a tool like GraphiQL or Altair. It’s similar to working with Postman for REST APIs (which also now has GraphQL support!). Be sure to also check if the API is already hosting a GraphQL console/explorer, which means you don’t even have to bring your own tools (we’ll talk more about this later).

这是一个非常基本的查询,但是随着查询和响应的增加,手动处理变得非常痛苦。当需要构造查询时,您需要使用一个工具,比如 GraphiQL 或 Altair。它类似于使用 Postman for REST api (它现在也支持 GraphQL!).确保还要检查 API 是否已经托管了 GraphQL 控制台/资源管理器,这意味着您甚至不必带上自己的工具(稍后将详细讨论)。

As a note, API requests are not limited to POST requests or even required to be JSON encoded. The query can be passed as parameters in a GET request or even as URL-encoded form data in a POST request. All of this depends on the implementation, and you should definitely explore to see what is available. The following requests are some examples of what the GraphQL API may accept:

注意,API 请求不仅限于 POST 请求,甚至也不需要 JSON 编码。查询可以在 GET 请求中作为参数传递,甚至可以在 POST 请求中作为 url 编码的表单数据传递。所有这些都取决于实现,您肯定应该探索看看有什么是可用的。下面的请求是 GraphQL API 可能接受的一些例子:

GET (query parameters)

GET (查询参数)

GET /graphql?query=%7B%0A++capsules+%7B%0A++++id%0A++%7D%0A%7D%0A&variables%5Bvariable1%5D=example HTTP/1.1
...
Connection: close

POST (URL encoded body)

POST (URL 编码体)

POST /graphql HTTP/1.1
...
Content-Type: application/x-www-form-urlencoded
Connection: close

query=%7B%0A++capsules+%7B%0A++++id%0A++%7D%0A%7D%0A&variables%5Bvariable1%5D=example

POST (form data)

POST (表格数据)

POST /graphql HTTP/1.1
...
Content-Type: multipart/form-data, boundary=---------------------------kanbvfbnmt
Connection: close

-----------------------------kanbvfbnmt
Content-Disposition: form-data; name="query"

{
  capsules {
    id
  }
}

-----------------------------kanbvfbnmt
Content-Disposition: form-data; name="variables[variable1]"

example
-----------------------------kanbvfbnmt--

Discovery

发现

A GraphQL schema or a working client is important to have. Think of a schema as API documentation generated and used by tools like Swagger or Postman; it provides type definitions, and GraphQL is statically typed, remember? Without this, you won’t know which queries you can make (unless you observe client-generated traffic).

一个 GraphQL 模式或一个正在工作的客户机非常重要。可以将模式看作是由 Swagger 或 Postman 等工具生成和使用的 API 文档; 它提供类型定义,而 GraphQL 是静态类型的,记得吗?否则,您将不知道可以进行哪些查询(除非您观察到客户端生成的流量)。

Introspection is Enabled

启用自省

An introspection query is a special query you can make against a GraphQL API that returns information about which queries it supports. A very basic introspection query is shown below:

自省查询是一种特殊的查询,您可以对返回关于它支持哪些查询的信息的 GraphQL API 进行查询。下面是一个非常基本的自省查询:

Request

请求

{
  __schema {
    types {
      name
    }
  }
}

Response

回应

{
  "data": {
    "__schema": {
      "types": [
        ...
        {
          "name": "Location"
        },
        {
          "name": "LaunchFind"
        },
        {
          "name": "LaunchesPastResult"
        },
        {
          "name": "Launchpad"
        },
        {
          "name": "MissionsFind"
        },
        {
          "name": "Mission"
        },
        {
          "name": "MissionResult"
        },
        {
          "name": "PayloadsFind"
        },
        {
          "name": "Roadster"
        },
        ...
      ]
    }
  }
}

However, you probably want to know more than just the names of the types available. The full query below can be used to get a lot more details:

但是,您可能想知道的不仅仅是可用类型的名称。下面的完整查询可以用来获得更多的细节:

query IntrospectionQuery {
      __schema {
        queryType { name }
        mutationType { name }
        subscriptionType { name }
        types {
          ...FullType
        }
        directives {
          name
          description
          locations
          args {
            ...InputValue
          }
        }
      }
    }

    fragment FullType on __Type {
      kind
      name
      description
      fields(includeDeprecated: true) {
        name
        description
        args {
          ...InputValue
        }
        type {
          ...TypeRef
        }
        isDeprecated
        deprecationReason
      }
      inputFields {
        ...InputValue
      }
      interfaces {
        ...TypeRef
      }
      enumValues(includeDeprecated: true) {
        name
        description
        isDeprecated
        deprecationReason
      }
      possibleTypes {
        ...TypeRef
      }
    }

    fragment InputValue on __InputValue {
      name
      description
      type { ...TypeRef }
      defaultValue
    }

    fragment TypeRef on __Type {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
                ofType {
                  kind
                  name
                  ofType {
                    kind
                    name
                  }
                }
              }
            }
          }
        }
      }
    }

If you get a response to an introspection query, you are halfway there. You will have the schema and can learn what is possible to do with the GraphQL API. This is usually enabled by default in most GraphQL implementations, and it often stays enabled for APIs intended for public consumption. An introspection query is sent automatically by GraphQL tools (like GraphiQL) to have the documented API ready for you.

如果您得到了对自省查询的响应,那么您已经完成了一半。您将拥有这个模式,并且可以了解使用 GraphQL API 可以做什么。在大多数 GraphQL 实现中,这通常是默认启用的,并且对于公共使用的 api,它通常保持启用状态。自省查询是由 GraphQL 工具(如 grahiql)自动发送的,以便为您准备好文档化的 API。

Introspection is Disabled

自省是残疾的

If introspection is disabled, you are not at the end of the road! You still have options. Let’s talk about what you can do:

如果内省是残疾的,你不是在路的尽头!你还有选择。让我们来谈谈你能做什么:

  1. If you have access to source code, you may have access to a file that contains a schema defined using schema definition language (SDL). With this file, you have the entire schema in human-readable form, which can easily be processed by other tools. On the other hand, a schema that is defined programmatically is not easily consumable by other tools and can be more difficult to use.

    如果可以访问源代码,则可以访问包含使用模式定义语言(SDL)定义的模式的文件。有了这个文件,您就拥有了人类可读形式的整个模式,可以很容易地被其他工具处理。另一方面,以编程方式定义的模式不容易被其他工具使用,而且可能更难使用。

  2. If you have access to the client-side source code (like a mobile app or JavaScript), then you can learn a lot about the queries implemented there as well. This requires more manual work and can be tedious. How long it will take to get useful information depends on whether the source code has been compiled, transpiled, obfuscated, and/or minified.

    如果您可以访问客户端的源代码(比如移动应用程序或 JavaScript) ,那么您也可以了解很多关于在其中实现的查询的信息。这需要更多的体力劳动,并且可能是乏味的。需要多长时间才能获得有用的信息取决于源代码是否已经编译、透露、混淆和/或缩小。

  3. Reversing client-side code doesn’t sound like fun? Well, just like when you don’t have API documentation, you could still observe traffic from the client and see which queries are made. However, this process makes it hard to see the big picture of what is available to you since you have to go through every request to understand how everything is connected.

    反向客户端代码听起来不好玩吗?就像没有 API 文档一样,您仍然可以观察来自客户端的流量,并查看发出了哪些查询。然而,这个过程使你很难看到什么是可用的,因为你必须通过每一个请求来理解所有的事情是如何连接的。

  4. If you don’t have access to a client or maybe you think there are additional queries that were not sent by the client, the last resort is brute-forcing. This is made much more effective through tools like clairvoyance or clairvoyancex, which use information leakage and helpful autocomplete features from GraphQL APIs to help build a schema. Although you may not build the complete schema through this method, you will have much more than you did before.

    如果您没有访问客户机的权限,或者您认为还有其他查询没有被客户机发送,那么最后的办法就是强制执行。通过诸如千里眼或千里眼之类的工具,这种方法变得更加有效,这些工具使用信息泄露和 GraphQL api 中有用的自动完成特性来帮助构建模式。尽管您可能无法通过此方法构建完整的模式,但您将拥有比以前更多的内容。

Visualization

可视化

Once you have the schema, you can really start digging into the API. Tools like Voyager are great for visualizing the schema and give you an idea of what you can do:

一旦有了模式,就可以开始深入研究 API 了。像 Voyager 这样的工具非常适合可视化模式,并且能让你知道你能做什么:

With this visualization, you can start coming up with a plan of attack to make sure you get coverage of everything as different roles, users, or organizations. Unfortunately, there isn’t much automation for this at the moment except for tools that already exist like AuthMatrix. Although they are not intended specifically for GraphQL, they can still be set up with HTTP requests containing a GraphQL query.

通过这个可视化,您可以开始制定一个攻击计划,以确保能够覆盖不同角色、用户或组织的所有内容。不幸的是,除了像 AuthMatrix 这样已经存在的工具之外,目前还没有多少自动化的工具。尽管它们并不是专门针对 GraphQL 的,但是仍然可以使用包含 GraphQL 查询的 HTTP 请求来设置它们。

Vulnerabilities

漏洞

As with every new technology, a lot of vulnerabilities that affect GraphQL are reincarnations of known issue types. This list is not exhaustive, but it includes things you should definitely look for:

与每一项新技术一样,许多影响 GraphQL 的漏洞都是已知问题类型的化身。这个列表并不是详尽无遗的,但是它包含了一些你绝对应该去寻找的东西:

Authentication and Authorization

认证和授权

  • In GraphQL there is a concept of a query context, which can be populated with user data for that request and then used for authorization checks. Depending on anything outside of this query context is asking for trouble. Determine how the GraphQL API is authenticating you as a user and whether details about you and your level of access are based on a session.

    在 GraphQL 中有一个查询上下文的概念,它可以用该请求的用户数据填充,然后用于授权检查。根据这个查询上下文之外的任何东西都是自找麻烦。确定 GraphQL API 如何对您进行用户身份验证,以及关于您和您的访问级别的详细信息是否基于会话。

  • Authorization controls should be placed in the business logic layer (although many times it just gets tacked on somewhere else). Many GraphQL APIs aren’t built from the ground up, but instead ported from existing APIs. This causes developers to get “creative” when adding or migrating authorization controls to a GraphQL API.

    授权控制应该放在业务逻辑层(尽管很多时候它只是在其他地方附加)。许多 GraphQL api 不是从头开始构建的,而是从现有的 api 中移植的。这使得开发人员在向 GraphQL API 添加或迁移授权控件时获得“创造性”。

  • Authorization should be handled by nodes and not as part of accessing an edge. The figure below shows a simple graph with user nodes and a post node, which is something you may see in a very basic social media site. The lines between the nodes represent relationships and are called edges. In GraphQL, when a field within a node references another node, that relationship is called an edge.

    授权应该由节点处理,而不是作为访问边缘的一部分。下面的图显示了一个简单的用户节点图和一个 post 节点图,这是你可以在一个非常基本的社交媒体网站上看到的。节点之间的线表示关系,称为边。在 GraphQL 中,当一个节点中的字段引用另一个节点时,这种关系称为边。

A user may have a field called AuthoredPosts, which represents all the different post nodes that the author created. Each of those post nodes would have an author field that represents the author that created that post.

用户可能有一个名为 authorredposts 的字段,该字段表示作者创建的所有不同的发布节点。每个帖子节点都有一个 author 字段,代表创建帖子的作者。

If a post is intended to be available only to friends of the author, the API might have an authorization check in the post node. The API can validate that the user trying to interact with the post is a friend of the author. But what if the check is instead performed upon accessing the author edge from a user node? This would prevent a user from accessing the post, but the user may still be able to like the post even if they are not a friend. Since the authorization check isn’t performed in the post node, an additional check for liking the post may have been forgotten. It gets tricky trying to track all the different authorization checks when performing them on edges.

如果一篇文章仅对作者的朋友可用,那么 API 可能在文章节点中有一个授权检查。该 API 可以验证试图与帖子交互的用户是作者的朋友。但是如果检查是在从用户节点访问作者边缘时执行的呢?这样可以防止用户访问帖子,但是即使他们不是好友,用户仍然可以喜欢这篇帖子。由于授权检查没有在 post 节点中执行,因此可能会忘记对 post 进行额外的点赞检查。在边缘上执行所有不同的授权检查时,跟踪这些检查是很棘手的。

Because there are usually different paths to getting to a node, you (as the tester) should check all the paths to a node in the case that authorization checks are performed on an edge and the check is missing. Voyager, a tool mentioned earlier, will help with figuring out those paths in a visual manner.

因为到达一个节点通常有不同的路径,所以您(作为测试人员)应该检查到达一个节点的所有路径,以防在边上执行授权检查而检查不到。“旅行者”是前面提到的一个工具,它将以可视化的方式帮助确定这些路径。

  • Not all fields may be intended to be accessible, even if it’s for your own user object. You don’t want users to be able to suddenly change their user role to become a super admin of the entire application. Try adding fields to a query or mutation and see if you are able to do something unintended. Also, compare fields between different roles to find new fields you didn’t know existed.

    并不是所有的字段都可以被访问,即使是针对您自己的用户对象。您不希望用户能够突然改变他们的用户角色,成为整个应用程序的超级管理员。尝试向查询或变异添加字段,看看是否能够做一些无意识的操作。此外,比较不同角色之间的字段,找到你不知道存在的新字段。

  • When integrating a GraphQL API with systems that implement their own authorization (another REST API), mistakes can be made when translating this access between services. Caching may also reveal data from other users.

    当将 GraphQL API 与实现自己授权的系统(另一个 REST API)集成时,在服务之间转换这种访问时可能会出错。缓存还可以显示来自其他用户的数据。

  • Some GraphQL engines will include a node or nodes field automatically. If these are present, be sure to check whether you can access something you’re not meant to have access to.

    一些 GraphQL 引擎将自动包含一个节点或节点字段。如果这些都存在,一定要检查你是否可以访问一些你不想访问的东西。

Mislabeled Operation Type

标记错误的操作类型

When compared to a REST API, a query operation type is like a GET request and a mutation operation type is like a POST request. We should expect that a query will not be used for state changes and will only be done as a mutation and vice-versa. This may not always be the case, and you may see operation types misused. The operation name will often be a dead giveaway as to whether it’s a state-changing query. Why is it an issue to misuse operation types? It causes confusion, and it also improves an attacker’s chances of achieving cross-site request forgery, which is mentioned later.

与 REST API 相比,查询操作类型类似于 GET 请求,而变异操作类型类似于 POST 请求。我们应该预期查询不会用于状态更改,只会作为变异进行,反之亦然。并非总是如此,您可能会看到操作类型被滥用。操作名称通常会暴露出它是否是一个状态更改的查询。为什么误用操作类型是一个问题?它会引起混淆,同时也提高了攻击者实现跨站请求伪造攻击的几率,这一点后面会提到。

Input Validation

输入验证

In addition to the built-in scalar types, custom scalars can be created and used in GraphQL. These custom scalars may not have had as many eyes on them, so hit these extra hard with some fuzzing. Make sure that they do the following:

除了内置标量类型之外,还可以在 GraphQL 中创建和使用自定义标量。这些定制标量可能没有那么多的眼睛看着他们,所以这些额外的努力与一些模糊。确保他们做到以下几点:

  • Restrict the input to proper character sets
  • 将输入限制为适当的字符集
  • Enforce integer ranges 实施整数范围
  • Handle null values, etc. 处理空值等等

If a custom scalar type is being used to encapsulate other complicated types like JSON or XML, this is an area you should focus on. It’s very likely that the contents in the encapsulated type are not being validated, which could lead to vulnerabilities.

如果使用自定义标量类型封装其他复杂类型(如 JSON 或 XML) ,则应该关注这一领域。很可能没有对封装类型中的内容进行验证,这可能会导致漏洞。

GraphQL is database agnostic and can be backed by a NoSQL database (MongoDB, Couchbase), relational database (MySQL, PostgreSQL), or even another API. Injections in these back-end systems can still occur whether it’s through type juggling or lax scalar types. Be sure to look for server-side errors when injecting (no)SQL or JSON syntax within queries. The more knowledge you have on the back ends behind a GraphQL API, the better. For example, if you have REST API calls made in the background with data you send to a GraphQL API, then you can try to inject extra parameters, get path traversal, or even perform server-side request forgery. There’s a lot of funky stuff that could be happening behind the scenes.

GraphQL 与数据库无关,可以由 NoSQL 数据库(MongoDB、 Couchbase)、关系数据库数据库(MySQL、 PostgreSQL)甚至另一个 API 进行支持。无论是通过类型变换还是 lax 标量类型,这些后端系统中的注入仍然可能发生。在查询中注入(不) SQL 或 JSON 语法时,一定要查找服务器端错误。您对 GraphQL API 的后端了解得越多越好。例如,如果在后台使用发送到 GraphQL API 的数据进行 REST API 调用,那么您可以尝试注入额外的参数,遍历路径,甚至执行服务器端请求伪造。有很多时髦的东西可能会发生在幕后。

GraphQL Batching

GraphQL 批处理

Remember how we said you can request multiple resources within one request? Well, that feature can also backfire if you start making too many queries in a single HTTP request; brute-force attacks, race conditions, and other unexpected behaviors can occur.

还记得我们说过您可以在一个请求中请求多个资源吗?如果您开始在单个 HTTP 请求中进行过多的查询,那么这个特性也可能会适得其反; 暴力破解攻击、竞争条件和其他意外行为可能会发生。

If API rate limiting is in place and enforced only at the HTTP level, a single HTTP request with many queries won’t be throttled. Brute-force attacks may suddenly become very easy! Interesting endpoints may be login or authentication-related (2FA), IDOR/enumerable objects, or other functionality specific to your application. A tool that can help with this is BatchQL, created by Assetnote. Otherwise, pull out your favorite scripting language and script an attack yourself.

如果 API 速率限制已经到位并且只在 HTTP 级别强制执行,那么一个带有多个查询的 HTTP 请求将不会被限制。暴力攻击可能突然变得非常容易!有趣的端点可能是登录或与身份验证相关的(2FA)、 IDOR/可枚举对象,或应用程序特有的其他功能。一个可以帮助解决这个问题的工具是 BatchQL,由 Assetnote 创建。否则,拿出你最喜欢的脚本语言,自己编写一个攻击脚本。

Race conditions can occur if mutations within the same HTTP request are performed in parallel instead of sequentially. This behavior will depend on the GraphQL engine that is being used. Of course, race conditions across multiple HTTP requests would also still apply. Because many applications are not built with atomic operations in mind, these vulnerabilities are very common.

如果同一 HTTP 请求中的变异是以并行方式而不是顺序方式执行的,则可能会出现竞争条件。这种行为将取决于所使用的 GraphQL 引擎。当然,跨多个 HTTP 请求的竞争条件仍然适用。由于许多应用程序在构建时没有考虑到原子操作,因此这些漏洞非常常见。

Denial of Service

分布式拒绝服务攻击

Complex queries can lead to denial of service (DoS) by making the GraphQL API take a long time to return the response to your query. This is not so different from regular expression DoS, although it’s trickier to prevent. Requesting a lot of data at once (or having a query that nests many other queries) could overload something in the chain, be it the application server, database, or other APIs behind the scene.

复杂的查询可能会导致分布式拒绝服务攻击查询(DoS) ,因为 GraphQL API 需要很长时间才能返回查询的响应。这与正则表达式 DoS 没有太大的不同,尽管要防止这种情况发生会更加困难。一次请求大量数据(或者拥有嵌套许多其他查询的查询)可能会使链中的某些内容超载,无论是应用程序服务器、数据库还是背后的其他 api。

Techniques that developers use to mitigate DoS in GraphQL include limiting the depth, size, or cost of a query.

开发人员用来减轻 GraphQL 中 DoS 的技术包括限制查询的深度、大小或成本。

For additional reading, Apollo wrote a blog post that reviews the thought process of trying to protect a GraphQL API from expensive queries.

为了进一步阅读,Apollo 写了一篇博客文章,回顾了试图保护 GraphQL API 免受昂贵查询的思维过程。

Cross-site Request Forgery (CSRF)

跨站请求伪造

Don’t skip checking for CSRF just because you see all of this JSON in requests. The GraphQL API may accept data in other forms besides plain JSON in a body.

不要仅仅因为在请求中看到了所有这些 JSON,就跳过对 CSRF 的检查。GraphQL API 可以在主体中接受普通 JSON 以外的其他形式的数据。

Ask yourself these questions:

问问你自己这些问题:

  1. Is the GraphQL API being used from a browser that performs cookie authentication and/or doesn’t require special headers?
  2. GraphQL API 是从执行 cookie 身份验证和/或不需要特殊头的浏览器中使用的吗?
  3. Are you able to perform mutations using GET parameters, POST body with URL encoded data, or POST body with form data?
  4. 您能够使用 GET 参数、使用 URL 编码数据的 POST 主体或使用表单数据的 POST 主体执行变异吗?

If you meet the conditions above and the stars align with SameSite, then you’re in luck!

如果你符合上面的条件,而且星星排列在同一个星座上,那么你就是幸运的!

For additional reading, Doyensec wrote a great blog post about CSRF issues in GraphQL, and they also released a tool that can help with generating various GET and POST requests.

为了进一步阅读,Doyensec 用 GraphQL 写了一篇很棒的关于 CSRF 问题的博文,他们还发布了一个工具,可以帮助生成各种 GET 和 POST 请求。

Introspection Enabled

启用自省

As someone testing a GraphQL API, you’ll definitely want introspection enabled. However, just as introspection is useful to you, an attacker is also going to get value out of it. Make it a bit harder for attackers by disabling introspection on production APIs, unless of course public API documentation is required. This is basically an information disclosure feature/misconfiguration. Additionally, once introspection is disabled, ensure that hinting is also disabled, as that type of information disclosure leads to schema discovery. For example, you may issue a query with a partial word like caps, which doesn’t exist:

作为测试 GraphQL API 的人,您肯定希望启用自省。然而,正如自省对您有用一样,攻击者也将从中获得价值。当然,除非需要公共 API 文档,否则通过禁用生产 API 的自省会让攻击者更加困难。这基本上是一个信息披露特性/错误配置。此外,一旦禁用了自省,请确保也禁用了暗示,因为这种类型的信息公开会导致模式发现。例如,你可能会发出一个查询,其中包含一个不存在的部分单词,比如 caps:

{
  caps {
    id
  }
}

The server may respond with an error message like the following:

服务器可能会响应如下的错误消息:

{
  "errors": [
    {
      "message": "Cannot query field \"caps\" on type \"Query\". Did you mean \"capsule\" or \"capsules\"?",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    }
  ]
}

This behavior can be used to more efficiently brute-force a schema. The tool clairvoyance, mentioned earlier, utilizes this functionality to enumerate the schema.

这种行为可以用来更有效地强制某个模式。前面提到的工具千里眼利用这个功能来枚举模式。

Console/Explorer Enabled

启用控制台/资源管理器

Depending on the GraphQL engine used, it may come packaged with a console for developers that isn’t disabled in production.

根据所使用的 GraphQL 引擎,它可能会为开发人员打包一个控制台,而不是在生产中禁用。

The following is GraphiQL, which is a common console you will see:

下面是 GraphiQL,这是一个常见的控制台,你会看到:

Here are some ideas for paths to look for: SecLists – GraphQL. You don’t want to have consoles like this enabled unless they are on a public GraphQL API, as this exposes an additional attack surface.

下面是一些查找路径的想法: SecLists-GraphQL。您不希望启用这样的控制台,除非它们使用的是公共 GraphQL API,因为这会暴露额外的攻击面。

Error Information Disclosure

错误信息披露

Error messages are helpful to any attacker. In the context of GraphQL, pay attention to error messages that leak names of fields. If you don’t have a schema, this can help reveal some of the inner workings of the GraphQL API.

错误消息对任何攻击者都有帮助。在 GraphQL 上下文中,请注意泄露字段名称的错误消息。如果没有模式,这可以帮助揭示 GraphQL API 的一些内部工作原理。

Conclusions

结论

Hopefully this post was a good springboard for you to use to jump into the waters of GraphQL! If you are looking to take things to the next level, be sure to go through Introduction to GraphQL. After that, go ahead and build a GraphQL API yourself and get into the same mindset as developers utilizing this new technology.

希望这篇文章是一个很好的跳板,你可以用它跳进 GraphQL 的水里!如果您希望进入下一个层次,请务必阅读 GraphQL 介绍。在此之后,继续自己构建一个 GraphQL API,并像使用这项新技术的开发人员一样进入相同的思维模式。


Forces Unseen is an independent security consulting firm with a focus on application and infrastructure security.

是一家专注于应用程序和基础设施安全的独立安全咨询公司。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

您正在使用您的 WordPress.com 账号评论。 注销 /  更改 )

Google photo

您正在使用您的 Google 账号评论。 注销 /  更改 )

Twitter picture

您正在使用您的 Twitter 账号评论。 注销 /  更改 )

Facebook photo

您正在使用您的 Facebook 账号评论。 注销 /  更改 )

Connecting to %s