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