什么是GraphQL
翻译
原文:https://portswigger.net/web-security/graphql/what-is-graphql
- name: 翻译
desc: 原文:https://portswigger.net/web-security/graphql/what-is-graphql
bgColor: '#F0DFB1'
textColor: 'green'
2
3
4
# 1什么是GraphQL?
GraphQL 是一种 API 查询语言,旨在促进客户端和服务器之间的高效通信。它使用户能够 在响应中准确指定 他们想要的那部分数据,有时在使用 REST API 时,这有助于避免出现大型响应对象和重复调用。
GraphQL 服务定义了一个协议,客户端可以通过该协议 与服务器进行通信。客户端不需要知道数据所在的位置。相反,客户端将查询发送到 GraphQL 服务器,该服务器从相关位置获取数据。由于 GraphQL 与平台无关,因此它可以使用多种编程语言实现,并且可以与几乎任何数据存储进行通信。
笔记
本页讲述了 GraphQL 是什么以及它是如何工作的。有关如何测试 GraphQL 漏洞的具体信息,请参阅GraphQL API漏洞 (opens new window)。
# 2GraphQL的工作原理
GraphQL 模式定义了服务数据的结构,可以列出可用的对象(也称为类型)、字段和关系。
根据 GraphQL 模式所描述的数据,可以使用三种类型的操作进行数据处理:
- 查询并提取数据。
- 变更可以被添加、更改或删除的数据。
- 订阅类似于查询,但它设置了一个持久性连接,服务器通过该连接,可以将指定格式的数据 主动推送到客户端。
所有 GraphQL 操作都使用相同的端点,并且通常作为 POST 请求发送。这与 REST API 有很大的不同,后者在一系列 HTTP 方法中使用特定于操作的端点。对于 GraphQL,操作的类型和名称定义了查询的处理方式,而不是将查询发送到指定的端点 或 使用不同的 HTTP 方法。
(((译者加:REST API 定义了多个端点,不同的操作需要使用不同的 HTTP 方法并请求不同的端点。而 GraphQL 只定义一个端点,所有的操作都使用同一个端点,GraphQL 会根据你发送的 类型/名称 来判断你具体要做哪些操作)))
GraphQL 服务通常会使用请求结构中的 JSON 对象来响应操作。
# 3什么是GraphQL模式?
在 GraphQL 中,模式(schema)用于表示服务器前端和后端之间的协议。它使用人类可读的语言来定义模式,并将可用的数据定义为一系列类型。然后,这些类型可以由服务实现。
定义的大多数类型都是对象类型,它定义了可用的对象以及它们所具有的字段和参数。每个字段都有自己的类型,可以是另一个对象,也可以是标量、枚举、联合、接口或自定义类型。
下面的示例展示了 Product 类型的简单模式定义。运算符!
表示该字段在调用时不可为空(必填项)。
#Example schema definition
type Product {
id: ID!
name: String!
description: String!
price: Int
}
2
3
4
5
6
7
8
模式中还必须包含至少一个可用查询。通常,它们还包含可用于变更的详细信息。
# 4什么是GraphQL查询?
GraphQL 查询从数据存储中检索数据。它们大致等同于 REST API 中的 GET 请求。
一般来说,查询通常由以下几个关键部分组成:
query
操作类型。这在理论上来说是可选的,但推荐你将其加上,明确地告诉服务器 传入的请求是一个查询。- 查询名称。这可以是你想要的任何东西。查询名称也是可选的,但建议加上,因为它有助于调试。
- 数据结构。这是查询应返回的数据。
- (可选)一个或多个参数。这些参数用于创建查询 并 返回特定对象的详细信息(例如,“为我提供 ID 为 123 的产品名称和描述”)。
下面的示例展示了一个名为myGetProductQuery
的查询,该查询请求id
为123
的产品名称和描述字段。
#Example query
query myGetProductQuery {
getProduct(id: 123) {
name
description
}
}
2
3
4
5
6
7
8
请注意,产品类型在模式中包含的字段,可能比此处请求的字段多。仅请求所需数据的能力是 GraphQL 灵活性的重要组成部分。
(((译者加:Product
类型可能不止name
和description
这两个字段,即使有成百上千个字段,你也可以通过 GraphQL 获取你所需要的部分数据,而不用一下子全部获取)))
# 5什么是GraphQL变更?
变更以某种方式改变数据,包括添加、删除或编辑数据。它们大致等同于 REST API 的 POST、PUT 和 DELETE 方法。
与查询一样,变更也具有一个操作类型、名称和返回的数据结构。然而,变更总是接受某种类型的输入。这可以是一个内联值,但实际上通常作为变量提供。
下面的示例展示了创建新产品的变更及其相关响应。在这种情况下,服务被配置为自动为新产品分配一个 ID,该 ID 已按请求返回。
#示例变更请求
mutation {
createProduct(name: "Flamin' Cocktail Glasses", listed: "yes") {
id
name
listed
}
}
2
3
4
5
6
7
8
9
10
#示例变更响应
{
"data": {
"createProduct": {
"id": 123,
"name": "Flamin' Cocktail Glasses",
"listed": "yes"
}
}
}
2
3
4
5
6
7
8
9
10
11
# 6查询和变更的组成部分
GraphQL 语法包含几个用于 查询和变更 的常见组件。
# 6.1字段
所有 GraphQL 类型都包含可查询的数据项,称为字段。当你发送一个查询或变更时,你可以指定希望 API 返回哪些字段。响应将反映请求中指定的内容。
下面的示例展示了一个查询,用于获取所有员工的 ID 和姓名详细信息及其关联的响应。在本例中,id
、name.firstname
和name.lastname
是请求的字段。
#请求
query myGetEmployeeQuery {
getEmployees {
id
name {
firstname
lastname
}
}
}
2
3
4
5
6
7
8
9
10
11
12
#响应
{
"data": {
"getEmployees": [
{
"id": 1,
"name" {
"firstname": "Carlos",
"lastname": "Montoya"
}
},
{
"id": 2,
"name" {
"firstname": "Peter",
"lastname": "Wiener"
}
}
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 6.2参数
参数是为特定字段提供的值。可以在模式中定义 可供接受的参数类型。
当你发送包含参数的查询或变更时,GraphQL 服务器会根据其配置来确定如何响应。例如,它可能会返回一个特定对象,而不是所有对象的详细信息。
下面的示例展示了一个getEmployee
请求,该请求将员工 ID 作为参数。在这种情况下,服务器只响应与该 ID 匹配的员工详细信息。
#带参数的示例查询
query myGetEmployeeQuery {
getEmployees(id:1) {
name {
firstname
lastname
}
}
}
2
3
4
5
6
7
8
9
10
#对查询的响应
{
"data": {
"getEmployees": [
{
"name" {
"firstname": Carlos,
"lastname": Montoya
}
}
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
笔记
如果使用用户提供的参数直接访问对象,则 GraphQL API 可能容易受到访问控制 (opens new window)漏洞的影响,例如不安全的直接对象引用(IDOR) (opens new window)。
# 6.3变量
变量使你能够传递动态参数,而不是直接在查询本身中使用参数。
基于变量的查询 与 使用内联参数的查询,具有相同的结构,但查询的某些方面单独取自基于 JSON 的变量字典。它们使你能够在多个查询中复用公共结构,只有变量本身的值会发生变化。
在构建使用变量的查询或变更时,你需要:
- 声明变量和类型。
- 在查询中的适当位置添加变量名称。
- 从变量字典中传递变量键和值。
下面的示例展示了 与上一个示例中 相同的查询,但这次 ID 作为变量传递,而不是作为查询字符串的直接部分传递。
#带变量的示例查询
query getEmployeeWithVariable($id: ID!) {
getEmployees(id:$id) {
name {
firstname
lastname
}
}
}
Variables:
{
"id": 1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在此示例中,变量在第一行中声明为$id: ID!
。字符!
表示这是此查询的必填字段。然后,它在第二行中被作为参数使用id:$id
。最后,在变量 JSON 字典中设置变量本身的值。有关如何测试这些漏洞的信息,请参阅 GraphQL API漏洞 (opens new window)。
# 6.4别名
GraphQL 对象不能包含多个同名属性。例如,以下查询无效,因为它尝试返回两个产品类型。
#无效的查询
query getProductDetails {
getProduct(id: 1) {
id
name
}
getProduct(id: 2) {
id
name
}
}
2
3
4
5
6
7
8
9
10
11
12
你可以使用别名来显式命名你希望 API 返回的属性,从而规避以上限制。通过别名,你可以在一个请求中 返回同一对象类型的多个实例。这有助于减少所需 API 的调用次数。
在下面的示例中,查询使用别名 为这两个产品分别指定了唯一名称。现在这个查询通过了验证,并返回详细信息。
#使用别名的有效查询
query getProductDetails {
product1: getProduct(id: "1") {
id
name
}
product2: getProduct(id: "2") {
id
name
}
}
2
3
4
5
6
7
8
9
10
11
12
#对查询的响应
{
"data": {
"product1": {
"id": 1,
"name": "Juice Extractor"
},
"product2": {
"id": 2,
"name": "Fruit Overlays"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
笔记
有效地使用具有变更的别名,使你能够在一个 HTTP 请求中发送多个 GraphQL 消息。
有关如何使用此技术,绕过某些速率限制控制的详细信息,请参阅使用别名绕过速率限制 (opens new window)。
# 6.5片段
片段是查询或变更的可重用部分。它们包含属于关联类型的字段子集。
定义后,它们就可以被包含在查询或变更中。如果它们随后被修改,则修改后的值 将被包含在调用片段的每个查询或变更中。
下面的示例展示了一个getProduct
查询,其中产品的详细信息包含在productInfo
片段中。
#片段示例
fragment productInfo on Product {
id
name
listed
}
2
3
4
5
6
7
#调用片段的查询
query {
getProduct(id: 1) {
...productInfo
stock
}
}
2
3
4
5
6
7
8
#包含片段中所定义字段的响应
{
"data": {
"getProduct": {
"id": 1,
"name": "Juice Extractor",
"listed": "no",
"stock": 5
}
}
}
2
3
4
5
6
7
8
9
10
11
12
# 7订阅
订阅是一种特殊类型的查询。它使客户端能够与服务器建立长期连接,这样服务器就可以向客户端推送实时更新,而不需要一直轮询数据。订阅主要用于对大型对象的微小更改,以及需要小型实时更新的功能(如聊天系统或协作编辑)。
与常规查询和变更一样,订阅请求定义了要返回的数据结构。
订阅通常使用 WebSocket (opens new window) 实现。
# 8内省
“内省” 是一个内置的 GraphQL 函数,可用于查询服务器以获取有关模式的信息。它通常被用于 GraphQL IDE 和文档生成工具等应用程序。
与常规查询一样,你可以指定要返回的响应字段和结构。例如,你可能希望响应仅包含可用于变更的名称。
内省可能具有严重的信息泄露 (opens new window)风险,因为它可用于访问潜在的敏感信息(例如字段描述)并帮助攻击者了解如何与 API 交互。“在生产环境中禁用内省” 是最佳实践。
笔记
有关如何使用 GraphQL 内省查询和测试内省漏洞的更多信息,请参阅 GraphQL API漏洞 (opens new window)。