GraphQL API漏洞
翻译
原文:https://portswigger.net/web-security/graphql
- name: 翻译
desc: 原文:https://portswigger.net/web-security/graphql
bgColor: '#F0DFB1'
textColor: 'green'
2
3
4
# 0GraphQL API漏洞
GraphQL 漏洞通常是由于实现和设计缺陷而产生的。例如,内省功能可能处于活动状态,使攻击者能够查询 API 以便收集有关其架构的信息。
GraphQL 攻击通常采用恶意请求的形式,使攻击者能够获取数据 或 执行未经授权的操作。这些攻击可能会产生严重影响,特别是 如果用户能够通过操纵查询或执行 CSRF攻击 (opens new window) 来获得管理员权限。此外,易受攻击的 GraphQL API 也可能导致信息泄露 (opens new window)问题。
在本节中,我们将了解如何测试 GraphQL API。如果你不熟悉 GraphQL,请不要担心 - 我们将随时介绍相关细节。我们还提供了一些实验室,以便你可以练习所学知识。
实验室
如果你已经熟悉GraphQL API漏洞背后的基本概念,并且只想在一些实际的、易受攻击的目标上练习和利用它们,那么你可以从下面的链接访问本主题中的所有实验室。
更多信息
有关 GraphQL 是什么及其工作原理的完整入门知识,请参阅我们的学习页面什么是GraphQL (opens new window)。
# 1查找GraphQL端点
在测试 GraphQL API 之前,首先需要找到其端点。由于 GraphQL API 对所有请求使用相同的端点,因此这是一条有价值的信息。
笔记
本节介绍如何手动探测 GraphQL 端点。但是,Burp Scanner (opens new window)可以在扫描过程中自动测试 GraphQL 端点。如果发现任何此类端点,则会报告 “GraphQL endpoint found” 问题。
# 1.1通用查询
如果你将query{__typename}
发送到任何 GraphQL 端点,它将在其响应中的某处包含字符串{"data": {"__typename": "query"}}
。这被称为通用查询,是探测某个 URL 是否对应于 GraphQL 服务的有用工具。
这个查询之所以有效,是因为每个 GraphQL 端点都有一个名为__typename
的保留字段,该字段以字符串形式返回查询对象的类型。
# 1.2通用端点名称
GraphQL 服务通常使用类似的端点后缀。在测试 GraphQL 端点时,你应该尝试将通用查询发送到以下位置:
/graphql
/api
/api/graphql
/graphql/api
/graphql/graphql
如果这些通用端点都没有返回 GraphQL 响应,你也可以尝试将/v1
追加到路径中(例如/api/v1/graphql
)。
笔记
GraphQL 服务通常会响应任何非 GraphQL 请求,并返回 “查询不存在” 等类似的错误。在测试 GraphQL 端点时,你应该牢记这一点。
# 1.3请求方法
尝试查找 GraphQL 端点的下一步是 - 使用不同的请求方法进行测试。
生产环境中 GraphQL 端点的最佳实践是,仅接受内容类型为application/json
的POST
请求,因为这有助于防范 CSRF 漏洞。但是,某些端点可能会接受替代方法,例如使用x-www-form-urlencoded
内容类型的GET
或POST
请求。
如果你无法向通用端点发送 POST 请求来找到 GraphQL 端点,请尝试使用替代 HTTP 方法重新发送通用查询。
# 1.4初始检查
发现端点后,可以发送一些测试请求,以更深入地了解其工作原理。如果端点正在为网站提供动力,请尝试在 Burp 的浏览器中探索 Web 界面,并使用 HTTP 历史记录来检查所发送的查询请求。
# 2利用未经过滤的参数
发现端点后,你可以开始寻找漏洞。测试查询参数是一个很好的起点。
如果 API 直接使用参数来访问对象,则可能容易受到访问控制 (opens new window)漏洞的影响。用户可以仅通过 提供与该信息相对应的参数,来访问他们不应该拥有的信息。这有时被称为不安全的直接对象引用(IDOR)。
更多信息
有关 GraphQL 参数的一般说明,请参阅参数 (opens new window)。
有关 IDOR 的详细信息,请参阅不安全的直接对象引用(IDOR) (opens new window)。
例如,以下查询将会请求在线商店的产品列表:
#产品查询示例
query {
products {
id
name
listed
}
}
2
3
4
5
6
7
8
9
返回的产品列表 仅包含所列出的产品。
#产品响应示例
{
"data": {
"products": [
{
"id": 1,
"name": "Product 1",
"listed": true
},
{
"id": 2,
"name": "Product 2",
"listed": true
},
{
"id": 4,
"name": "Product 4",
"listed": true
}
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
从这些信息中,我们可以推断出以下几点:
- 产品被分配了一个顺序 ID。
- 列表中缺少 ID 为 3 的产品,可能是因为它已被下架。
通过查询缺失产品的 ID,我们可以获取其详细信息,即使它没有在商店中展出,也没有被原始的产品查询请求返回。
#查询并获取缺失的产品信息
query {
product(id: 3) {
id
name
listed
}
}
2
3
4
5
6
7
8
9
#缺失的产品响应
{
"data": {
"product": {
"id": 3,
"name": "Product 3",
"listed": no
}
}
}
2
3
4
5
6
7
8
9
10
11
# 3发现模式信息
测试 API 的下一步是 - 将有关底层模式的信息拼凑在一起。
执行此操作的最佳方法是使用内省查询。内省是一个内置的 GraphQL 函数,可用于查询服务器以获取有关模式的信息。
内省可以帮助你了解如何与 GraphQL API 进行交互。它还可以泄露潜在的敏感数据,例如描述字段。
# 3.1使用内省
要使用内省来发现模式信息,请查询__schema
字段。此字段适用于所有查询的根类型。
与常规查询一样,你可以在运行内省查询时,指定要返回的响应字段和结构。例如,你可能希望响应中仅包含可用于变更的名称。
# 3.2探测内省
“在生产环境中禁用内省” 是最佳实践,但有的人并不总是遵循此建议。
你可以使用以下简单查询来探测内省。如果目标启用了内省,则响应将返回所有可用于查询的名称。
#内省探测请求
{
"query": "{__schema{queryType{name}}}"
}
2
3
4
5
6
笔记
Burp Scanner可以在扫描过程中自动测试内省。如果它发现了已启用的内省,则会报告 “GraphQL introspection enabled” 问题。
# 3.3运行完整的内省查询
下一步是针对端点运行一个完整的内省查询,以便你可以获取 尽可能多的有关于底层模式的信息。
以下示例查询,返回了所有有关于查询、变更、订阅、类型和片段的完整详细信息。
#完整内省查询
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation #经常需要删除才能运行查询
onFragment #经常需要删除才能运行查询
onField #经常需要删除才能运行查询
}
}
}
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
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
笔记
如果启用了内省,但上述查询没有运行,请尝试从查询结构中删除onOperation
、onFragment
和onField
指令。许多端点不接受这些指令(作为内省查询的一部分时),删除它们之后,你通常可以更成功地进行内省。
# 3.4可视化内省结果
内省查询的响应中 可能包含大量信息,这通常很长且难以处理。
你可以使用 GraphQL可视化工具 (opens new window),从而更轻松地查看模式实体之间的关系。这是一个在线工具,它获取内省查询的结果,然后生成并返回数据的可视化表示形式,包括操作和类型之间的关系。
# 3.5使用InQL
作为手动运行内省查询 并 可视化结果的替代方法,你可以使用 Burp Suite 的 InQL (opens new window) 扩展。
InQL 是一个 Burp Suite 扩展,可以帮助你安全地审计 GraphQL API。当你向它传递一个 URL(通过提供一个实时端点链接或上传一个 JSON 文件)时,它会发出一个内省查询,请求所有查询和变更,并提供一个结构化视图,以便轻松浏览结果。
更多信息
有关在 Burp Suite 中使用 InQL 的更多信息,请参阅在Burp Suite中使用GraphQL (opens new window)。
# 3.6建议
(((译者加:???好像是叫 “建议”,应该可能大概也许没......错吧)))
即使完全禁用了内省,你有时也可以使用 “建议” 来收集有关 API 结构的信息。
建议是 Apollo GraphQL 平台的一项功能,其中服务器可以在错误消息中 提供当前查询的修改建议。这些通常用于查询中略有错误但仍可识别的情况(例如There is no entry for 'productInfo'. Did you mean 'productInformation' instead?
)——翻译后:没有 'productInfo' 条目,你的意思是 'productInformation' 吗?
你可以在其中收集潜在的有用信息,因为这种响应 有效地泄露了模式的有效部分。
Clairvoyance (opens new window) 是一个工具,它使用 “建议” 来自动恢复全部或部分 GraphQL 模式,即使在禁用了内省的情况下也是如此。这样一来,从建议响应中拼凑信息所花费的时间就大大减少了。
你无法直接在 Apollo 中禁用建议。相关解决方法请参阅此 GitHub 项目 (opens new window)。
笔记
Burp Scanner 可以在扫描过程中自动测试 “建议”。如果找到活动状态下的建议,Burp Scanner 会报告 “GraphQL suggestions enabled” 问题。
- name: 实验室-学徒
desc: 访问私有GraphQL帖子 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/graphql/lab-graphql-reading-private-posts
bgColor: '#001350'
textColor: '#39d50c'
2
3
4
5
6
- name: 实验室-从业者
desc: 意外暴露私有GraphQL字段 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/graphql/lab-graphql-accidental-field-exposure
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 4绕过GraphQL内省防御
如果无法为正在测试的 API 运行内省查询,请尝试在__schema
关键字后面插入特殊字符。
当开发人员禁用内省时,他们可以使用正则表达式,来排除查询中的__schema
关键字。你应该尝试空格、换行符和逗号等字符,因为它们会被 GraphQL 忽略,但有缺陷的正则表达式不会忽略它们。
(((译者加:又是欺负正则的一天)))
因此,如果开发人员仅排除了__schema{
,则以下内省查询不会被排除。
#使用换行符的内省查询
{
"query": "query{__schema
{queryType{name}}}"
}
2
3
4
5
6
如果这不起作用,请尝试使用其他请求方法运行探测,因为只有通过 POST 方法才能禁用内省。尝试 GET 请求或内容类型为x-www-form-urlencoded
的 POST 请求。
下面的示例显示了一个通过 GET 发送的内省探测,其中带有经过 URL 编码的参数。
# 作为 GET 请求的内省探测
GET /graphql?query=query%7B__schema%0A%7BqueryType%7Bname%7D%7D%7D
2
3
笔记
如果一个端点只接受通过 GET 发送的内省查询,同时你希望使用 InQL Scanner 分析查询结果,那么你首先需要将查询结果保存到一个文件中。然后,内可以将此文件加载到 InQL 中,在那里它将被正常解析。
- name: 实验室-从业者
desc: 查找隐藏的GraphQL端点 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/graphql/lab-graphql-find-the-endpoint
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 5使用别名绕过速率限制
通常,GraphQL 对象不能包含具有同一名称的多个属性。通过别名,你可以显式命名 API 返回的属性,从而规避此限制。你可以在一个请求中使用别名,返回同一类型对象的多个实例。
更多信息
有关 GraphQL 别名的更多信息,请参阅别名 (opens new window)。
虽然别名旨在限制 你所需要进行的 API 调用次数,但它们也可用于暴力破解 GraphQL 端点。
许多端点都会有某种速率限制器,以防止暴力攻击。一些速率限制器基于所接收的 HTTP 请求数来工作,而不是基于在端点上执行的操作数。由于别名可以有效地使你在单个 HTTP 消息中发送多个查询,因此它们可以用来绕过此限制。
下面的简化示例展示了一系列别名查询,用于检查商店折扣代码是否有效。此操作可能会绕过速率限制,因为它是单个 HTTP 请求,即使它可能会被用于一次性检查大量折扣代码。
#使用别名查询的请求
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- name: 实验室-从业者
desc: 绕过GraphQL暴力破解保护 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/graphql/lab-graphql-brute-force-protection-bypass
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 6GraphQL与CSRF
跨站请求伪造(CSRF)漏洞使攻击者能够 诱使用户执行他们不打算执行的操作。这是通过创建一个恶意网站来实现的,该网站伪造了对易受攻击的应用程序的跨域请求。
更多信息
有关 CSRF 漏洞的详细信息,请参阅CSRF学院主题 (opens new window)。
GraphQL 可以被用作 CSRF 攻击的载体,攻击者借此创建漏洞,导致受害者的浏览器 以受害用户身份 发送恶意查询。
# 6.1GraphQL上的CSRF漏洞是如何产生的?
如果 GraphQL 端点未验证发送给它的请求内容类型,并且没有实现 CSRF 令牌,则可能会出现 CSRF 漏洞。
只要验证了内容类型application/json
,则使用此内容类型的POST
请求就可以防止伪造。在这种情况下,即使受害者访问恶意站点,攻击者也无法让受害者的浏览器发送此请求。
但是,其他方法(如GET
)或内容类型为x-www-form-urlencoded
的任何请求,都可以由浏览器发送。因此,如果端点接受这些请求,则用户可能会易受攻击。在这种情况下,攻击者可能利用漏洞向 API 发送恶意请求。
笔记
对于基于 GraphQL 的 CSRF 漏洞,构建 CSRF攻击 (opens new window) 和提供漏洞利用的步骤与 “常规” CSRF 漏洞相同。有关此过程的详细信息,请参阅如何构造 CSRF 攻击 (opens new window)。
- name: 实验室-从业者
desc: 通过GraphQL执行CSRF漏洞利用 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/graphql/lab-graphql-csrf-via-graphql-api
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 7防范GraphQL攻击
若要防范许多常见的 GraphQL 攻击,在将 API 部署到生产环境时,请执行以下步骤:
- 如果你的 API 不打算提供给公众使用,请对其禁用内省。这使得攻击者难以获取有关 API 工作原理的信息,并降低了不必要的信息泄露风险。有关如何在 Apollo GraphQL 平台中禁用内省的信息,请参阅这篇博客文章 (opens new window)。
- 如果你的 API 旨在提供给公众使用,那么你可能需要启用内省。但是,你应该检查 API 的模式,以确保它不会向大众公开非预期的字段。
- 确保 “建议” 已被禁用。这可以防止攻击者使用 Clairvoyance 或类似的工具来收集有关底层模式的信息。相关解决方法请参阅此 GitHub 项目 (opens new window)。
- 确保你的 API 模式不会公开任何私有用户字段,例如电子邮件地址或用户 ID。
# 7.1防范GraphQL暴力攻击
使用 GraphQL API 时,有时可以绕过标准速率限制。有关此方面的示例,请参阅使用别名绕过速率限制 (opens new window)一节。
考虑到这一点,你可以采取一些设计步骤来保护你的 API 免受暴力攻击。这通常涉及限制 API 所接受的查询复杂性,并减少攻击者执行拒绝服务(DoS)攻击的机会。
要防御暴力攻击,请执行以下操作:
- 限制 API 查询的查询深度。术语 “查询深度” 指的是查询中的嵌套层级数。大量嵌套的查询 可能会对性能产生重大影响,如果接受这种查询,可能会为 DoS 攻击提供机会。通过限制 API 所能接受的查询深度,可以减少发生这种情况的可能性。
- 配置操作限制。通过操作限制,你可以配置 API 所能接受的唯一字段、别名和根字段的最大数量。
- 配置查询可以包含的最大字节数。
- 请考虑在 API 上实现成本分析。成本分析是一个过程,库应用程序在收到查询时,会识别 运行查询 所需要消耗的资源成本。如果查询的计算过于复杂而无法运行,则 API 会将其删除。
更多信息
有关如何在 Apollo 中实现这些功能的信息,请参阅这篇博客文章 (opens new window)。
# 7.2防范基于GraphQL的CSRF
要专门防御 GraphQL CSRF 漏洞,请在设计 API 时确保以下几点:
- 你的 API 仅接受通过 JSON 编码的 POST 查询。
- API 验证所提供的内容 是否与 所提供的内容类型相匹配。
- 该 API 具有安全的 CSRF 令牌机制。