原文:https://www.vaadata.com/blog/graphql-api-vulnerabilities-common-attacks-and-security-tips/
前言:
GraphQL(图形查询语言)由 Facebook 于 2012 年开发,并于 2015 年开源,自 2019 年起一直归 GraphQL 基金会管理。GraphQL 是一种查询语言,即用于访问数据库或任何其他信息系统中的数据的语言,方式与 SQL (结构化查询语言)相同。它也是一种 SDL(规范和描述语言)。其创建者没有提供官方实现。各种现有实现(Apollo Server、Express GraphQL、graphql-yoga 等)遵循与 GraphQL 相关的规范。与所有 API 一样,GraphQL 允许在客户端和服务器之间传输数据。它是 REST(表述性状态转移)API 的替代方案。GraphQL 的主要优势在于它可以通过单个请求提供应用程序,将构造数据的任务委托给服务器。2023 年,Postman 进行的一项调查显示,GraphQL API 是第三大最广泛使用的 API 架构。因此,渗透测试人员在渗透测试过程中会经常遇到这种类型的 API。
在本文中,我们将研究 GraphQL API 的工作原理、此类系统常见的漏洞和攻击,以及为保护系统而实施的最佳实践和措施。我们还将研究 GraphQL API 渗透测试期间使用的方法和工具。
GraphQL 的结构:模式和类型
在深入利用与此类 API 相关的各种漏洞之前,花点时间了解它是很重要的。
从审计员的角度来看,彻底了解 GraphQL 的工作原理至关重要。
因此,我们将首先详细介绍 GraphQL 的结构和主要概念,然后再研究各种可能的攻击媒介和可利用的漏洞。
GraphQL 架构
架构是 GraphQL API 的核心元素。它是基本结构,定义服务器和客户端之间所有可能的交互。对于给定的 API,只有一个架构作为参考。该模式指定了客户端可以提交的请求类型、可以从服务器检索的数据类型以及这些不同类型的数据之间的关系。
所有处理的数据都按“类型”进行组织。现在,我们将研究最常见的类型以及渗透测试期间我们关注的重点类型。
标量类型
标量类型是 GraphQL 中最基本的数据类型。它表示分配给字段的原始数据:
String:一个字符串
Int:整数
浮点数:带有小数点的数字
布尔值:真/假值
ID:唯一标识符
下面的示例说明如何使用标量类型定义Book对象,该对象具有三个字段,类型分别为String、String和Int:
这些是我们稍后将看到的更复杂类型的构建块。
对象类型
对象类型是使用 GraphQL 建模复杂数据结构的核心构造。GraphQL API 中定义的绝大多数类型都是对象。
对象由字段组成,每个字段都有自己的关联数据类型。此类型可以是用于原始数据的标量(String、Int 等),也可以是用于表示嵌套数据的另一个对象。
还可以使用其他更高级的类型,例如枚举、联合或接口,但这里我们不会对它们进行讨论。
仍旧举前面的例子,Book是一个仅由标量字段组成的对象。
我们可以想象一个更复杂的对象,Library。这将允许我们引入列表和非空,并发现对象的稍微复杂的实现。
符号[Type!]表示列表的元素必须为 Type类型。感叹号表示此字段不能为空(注意不要将空列表与空列表混淆)。
对象是 GraphQL 中的基本构建块,使得数据模型能够结构化。
特殊类型:查询和突变
虽然 GraphQL 模式中定义的大多数类型都是对象,但也有两种特殊类型:查询和突变。
这些是我们将用来检索或修改数据的类型。
查询类型
查询用于从 GraphQL 服务器检索数据。它遵循 GraphQL 的基本原则,仅返回查询中明确请求的字段。
以下是从Book对象中检索某些字段的基本查询示例:
这里我们有一个非常基本的查询,称为GetLibrary,我们明确请求某些字段。作为响应,我们将仅获取请求的字段。
通过添加参数、变量、片段、指令等,查询可以变得更加复杂。我们不会在这里讨论这些细节。
突变类型
另一方面,Mutations可用于修改服务器端的数据(创建、更新、删除)。Mutations 还可以在其响应中返回数据。
假设我们想在图书馆中添加一本新书。我们可以使用如下的变更:
此突变允许我们将书籍添加到图书馆。AddBookInLibrary指定此突变的预期字段,addBook是将要执行的字段。
作为回报,我们请求标题字段,如果请求成功,该字段将在响应中返回。
与查询一样,突变也可以非常复杂,并根据需要进行嵌套。但这个简单的例子说明了它们修改数据的主要作用。
我们现在已经了解了 GraphQL 的基础知识。彻底理解这些概念对于掌握这门语言和使用它优化 API 的渗透测试至关重要。
GraphQL API 测试
在深入了解问题的核心之前,重要的是要记住,GraphQL API 的渗透测试通常遵循与其他类型 API 相同的规则。
因此,测试方法将类似。但是,由于 GraphQL 有其自身的特性,因此某些阶段和攻击媒介是独一无二的。
除非客户提供完整的 API 文档,否则侦察阶段对于评估攻击面是必要的。我们将在下面描述可用的工具和技术。
获得这些信息后,我们将进入漏洞识别部分。在这里,我们将测试所有 API 的常见漏洞(注入、访问控制缺陷、可访问数据的暴露等)以及 GraphQL 特有的漏洞。
发现 GraphQL 端点
渗透测试 GraphQL API 的第一步是发现其端点。这并不总是显而易见的,特别是当 API 仅用于某些功能或特定角色时。
因此,我们将对目标进行模糊测试以找到端点,例如使用这个Seclists 单词列表,查询主体为“query{__typename}”。
如果我们收到包含{“data”:{“__typename”: “Query”}}的响应,则这将确认测试的 URL 上存在 GraphQL API。
或者,您可以简单地合法使用该应用程序,然后 graphql 端点就会被发现。
一旦发现端点,我们就可以开始模式发现和枚举。
GraphQL API渗透工具?
在 GraphQL API 渗透测试期间,审计人员可以依靠各种工具来在不同阶段促进他们的任务。
以下是您需要了解的主要工具的概述:
Introspection
Introspection不是一种工具,而是一种 GraphQL 功能。它用于检索 API 的完整架构,该架构定义了其数据结构。
我们将使用这个内省请求:
{__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}}}}}}}}
如果启用,此请求将返回一个 JSON 响应,详细说明模式中定义的所有类型、字段、参数等。
访问完整的模式非常有用,并且可以完成枚举阶段。
一旦我们掌握了这些信息,就有可能识别出潜在的敏感区域或不应公开的数据。
但是,出于安全原因,在生产中,GraphQL API 上的自省功能通常是被禁用的(理论上)。
Clairvoyance
当在 GraphQL API 上禁用模式自省时,可以使用Clairvoyance工具作为替代方案来尝试重建模式。
它通过使用单词表来工作。
通过发送大量请求,该工具依赖 GraphQl 的建议功能。需要注意的是,建议可能不会在客户端返回,从而导致该工具完全无效。
通过分析这些响应,Clairvoyance 能够以 JSON 的形式重建部分 API 模式。
尽管不如内省那么完整,但这种技术提供了对模式的良好概述。
GraphQL Voyager
GraphQL Voyager是一种有价值的工具,它可用于基于我们能够使用自省或 Clairvoyance 检索到的模式来可视化 GraphQL API 的模式。
可以从其界面检索内省查询。
加载模式后,该工具将生成模式结构的图形表示。所有类型、字段和关系都会显示出来,让您更容易理解 API 架构。
左下方的面板可用于更详细地探索可用查询、变异和订阅的列表。
简而言之,GraphQL Voyager 对于快速识别要调查的兴趣领域非常有用。
Postman
Postman最初是为开发和测试 API 而设计的工具。它在审计过程中的使用可以证明是一项宝贵的资产,其直观的界面方便重复请求。
在 Postman 中配置 GraphQL 端点后,它将自动执行自检请求并生成 API 的交互式文档。所有类型、查询、变异和订阅均已列出。
专用界面可让您通过选择所需的字段和参数来构建和重放查询。还可以轻松定义变量。
因此,使用此工具进行 API 渗透测试可以大大简化审计员的工作。
查询语言
InQL是 Burp Suite 代理的扩展,旨在测试 GraphQL API。就功能而言,它与 Postman 类似。可以从 Burp BApp Store 的扩展部分安装它。
可以通过两种不同的方式执行自省请求来检索模式。
第一个,直接来自 GraphQL:
第二步,从 InQL 选项卡:
无论我们使用哪种方法,我们都可以从扩展接口访问查询和变异列表。它列出了所需的参数和可用的字段。
然后,我们可以将这些查询直接发送到 Repeater 面板,以便在所选路线上运行测试。
graphql-cop
Graphql-cop 列出了 GraphQL API 中可能存在的主要漏洞。它是一个可在 GitHub 上获得的开源工具。但是,自 2022 年以来它一直没有得到维护。
这是非常容易使用:
只需指定目标的 URL,该工具就会尝试查找默认的 graphql 路径。请注意,路径列表非常有限:
因此最好不要依赖此工具来查找 graphql 端点。
然后将进行一系列测试,包括:
内省
建议字段
批量查询
别名重载
还有很多。
完成此操作后,我们就可以查看目标所受漏洞列表。
graphw00f
我们在这里讨论的最后一个工具是graphw00f。如前所述,GraphQL 没有自己的实现,需要执行引擎才能工作。
这就是 graphw00f 工具的作用所在。它的工作原理是向 API 发送专门设计的 GraphQL 请求,并能够根据返回的错误或元数据中存在的唯一签名来识别引擎。
从那里,我们可以参考这个表格,它根据实现来识别潜在的漏洞:
正如 graphql-cop 所提到的,查找路径的端点列表是有限的:
因此,最好在使用之前先识别它,或者使用 -w 标志指定要使用的自定义单词列表。我们将使用 -t 指定目标的 URL,成功识别后我们将获得如下输出:
GraphQL API 上最常见的漏洞和攻击有哪些?
至此,我们的目标枚举已经完成,现在我们需要进入测试可用路线的阶段。
我们在审核期间会进行许多测试,我们不会在这里介绍所有测试,但我们会研究其中的一些测试,以便更好地了解它们的工作原理。
其中一些测试将直接与 GraphQL 的架构和操作相关,而其他测试则通常涉及 API。
拒绝服务 (DoS) 攻击
DoS 攻击的目的是使目标服务器超载,使其在攻击期间速度变慢甚至无法访问。
这对其他用户的体验有很大影响,阻止他们使用,同时也损害了公司的形象。
如果查询深度没有限制,GraphQL API 特别容易受到此类攻击。
为了说明这一点,请考虑下图:
当我们发出 getPaste 请求时,请求中引用了所有者,而请求本身又引用了 pastes 等,对于服务器来说,这个请求可能会变得非常繁重。这样的结构可能会影响应用程序的平稳运行。
服务器返回的对象数量会随着深度的增加而呈指数增长,从而导致过载。
我们可以看到服务器瞬间过载,导致响应时间极长(近 12 秒)。在此期间,服务器不可用。
这是因为该查询对资源的要求特别高。更深层次的查询可能会导致服务器长时间不可用,甚至可能导致完全关闭。
需要注意的是,这些测试是在我们自己的机器上本地进行的,这解释了观察到的显著影响。一般来说,公司服务器更强大,可以处理多个同时的请求(理论上)。
然而,让我们考虑这样一种情况,即这种攻击同时从多台机器发起;这可能会导致相同的后果。
批量查询和别名
批量查询和别名是另一个可能在 GraphQL 中造成漏洞的方面。虽然可以从拒绝服务的角度考虑它们,但在本例中,我们将在暴力攻击场景中使用它们。
假设客户对登录表单设置了 HTTP 请求限制,每秒仅允许从同一 IP 地址发送 10 个请求。此措施旨在抵御对登录表单的暴力攻击。
如果授权批量查询或别名,这不足以阻止攻击者。攻击者可以在单个 HTTP 请求中发送多个请求,从而绕过限制并成功进行攻击。
这种方法大大加快了暴力攻击的速度。通过发送 100 个请求(每个请求包含 100 个别名或批量查询),攻击者已经进行了 10,000 次身份验证尝试。
增加单个 HTTP 请求中的请求数量会成倍地提高攻击的有效性。
身份验证和授权失效
使用白名单或黑名单来授权或禁止用户发出 GraphQL 请求是一种常见做法。但是,如果实施不当,这种方法可能会很脆弱。
假设有一个 GraphQL API,它公开了一个用于检查服务器状态的 systemHealth 请求。此请求需要以管理员身份进行身份验证。如果没有有效的身份验证令牌,请求将被拒绝,这是预期行为。
但是,在查询应该受保护的 systemHealth 字段时,可以使用没有身份验证令牌的授权操作名称来绕过此限制。
例如,假设未经身份验证的用户被授权执行 getPastes 请求。攻击者可以发送以下请求:
这里出现的问题是,请求的操作名称需要经过授权检查,而请求的资源则不需要。
在此请求中,操作名称 getPastes 已授权给未经身份验证的用户。然而,攻击者还包含了他本无权访问的 systemHealth 字段。
这里的问题是,授权检查仅针对操作名称进行,而没有对请求的资源进行单独检查。
常见的 API 漏洞和攻击
尽管 GraphQL 引入了特定的漏洞,但不应忘记 GraphQL API 本质上是一个 Web API。
因此,它可能与其他类型的 API 一样容易受到相同类型的漏洞攻击。我们将回顾审计期间可能遇到的 3 个漏洞。
存储型 XSS(跨站点脚本)
存储型跨站点脚本 (XSS) 漏洞可能会造成严重后果。它允许攻击者注入恶意 JavaScript 代码,这些代码将存储在服务器端,通常是在数据库中。然后,此恶意代码将在任何用户的浏览器中执行,其中会反映出恶意值。
其后果可能包括网页毁损甚至用户账户被盗。
要了解有关 XSS 漏洞的更多信息,请参阅我们的文章:XSS(跨站点脚本)漏洞:原理、攻击类型、漏洞利用和安全最佳实践。
让我们以论坛平台为例,用户可以发布消息。这些消息随后被采纳并显示在 Web 应用程序的公共页面上。
该功能的预期用途如下:
现在让我们添加一个恶意用户,他决定测试此应用程序并发布一条恶意消息。我们可以看到评论是使用 API 发送的,但我们也可以看到它在没有验证用户输入的情况下就被存储了。
然后,受感染的内容被接管并公开显示,而不对特殊字符进行编码,因此 Javascript 代码被解释并执行,从而使网站的合法用户受到攻击:
任意文件上传和路径遍历
API 渗透测试期间可能遇到的另一种类型的漏洞是任意文件上传与路径遍历相结合。
如果正确执行且满足条件,此漏洞可能会带来灾难性的后果,允许恶意文件写入服务器。
在探索过程中,我们发现了一个允许将文件上传到服务器的变更。此变更需要 2 个输入字段,即文件的名称和文件内容,并将返回结果。
经典上传可以正常工作,并且会返回已上传文件内容的响应。
如果你查看服务器端发生的情况,你会发现一切进展顺利,并且你会在 /opt/dvga/pastes 找到这个新文件:
通过将文件名更改为“../../../tmp/pwn”,查询仍然有效,但我们发现该文件不在预期路径中。相反,我们可以在 /tmp 文件夹中找到它。
如果我们查看负责保存文件的源代码,我们会发现没有检查文件名,这解释了它存在于其他文件夹中。
远程命令执行(RCE)
渗透测试中可以发现的最关键漏洞之一是在远程服务器上执行命令或 RCE。这可能导致许多后果,包括数据盗窃、权限提升、设置后门等。
我们将使用 importPaste 变异来利用此漏洞。
最初,此功能旨在从用户输入的 URL 导入数据,以便将其保存在服务器上。
从安全角度来看,这种功能的实现需要特别小心,而且这是审计人员特别感兴趣的功能。
实现如下:
相应的 GraphQL 变异需要不同的参数、主机、端口、路径和方案。因此,“格式正确”的查询可能如下所示:
现在考虑一个恶意用户决定不遵守预期,并将路径参数中的“/”替换为“/; sleep 10”。
这样,服务器最终执行的命令就变成了:“curl http://example.com:80/;sleep 10”,两个命令都会执行。
如果我们看一下服务器端,我们可以证实这一理论。
因此,攻击者可以尝试提升其权限、在内部网络中枢转、中断应用程序的平稳运行等。
如何保护 GraphQL API?
现在我们已经了解了 GraphQL、它的工作原理以及审计期间可能遇到的主要漏洞。
正如我们所见,GraphQL 并不能免受影响传统 Web API 的攻击媒介的影响,而且还存在特定于其实现的漏洞。因此必须采取严格的安全措施。
一般建议
为了从全局角度确保 GraphQL API 的保护,可以实施多种保护措施。
默认情况下,GraphQL 实现具有应该更改的默认配置:
禁用自省。大多数实现中默认启用此选项,但除非认为有必要,否则应将其禁用。
禁用 GraphiQL。这是 GraphQL 的一个实现,在应用程序开发阶段很有用,但除非有必要,否则不应出现在生产应用程序中。
这不一定在所有实现上都可行,或者意味着实施“定制”解决方案,但使建议无法访问,以防止攻击者扩大攻击范围。
防止拒绝服务
如果配置不正确,GraphQL 特别容易受到拒绝服务攻击。此类攻击会影响 API 的可用性和稳定性,使其速度变慢甚至不可用。
完整建议
以下是可以遵循的一些建议,以防止此类攻击:
定义查询的最大深度
定义响应中可返回的数据量的最大限制
按 IP、用户或两者对传入请求应用吞吐量限制
防止批量攻击
如前所述,批处理攻击可用于进行暴力攻击。为了防御此类攻击,有必要对传入请求施加限制:
对同一请求中的最大对象数量进行限制
限制可以同时执行的查询数量
防止对敏感查询/变异进行对象批处理
一种解决方案可能是在代码级别创建并发请求限制,限制用户可以请求的对象数量。这样,如果达到限制,用户的其他请求将被阻止,即使它们包含在同一 HTTP 请求中。
用户输入验证
发送到 GraphQL API 的用户输入必须经过严格验证。此输入通常会在多个上下文中重复使用,无论是 HTTP、SQL 还是其他请求。如果验证不正确,可能会导致注入漏洞。
验证用户输入
使用 GraphQL 特定类型,例如标量或枚举。如果您需要验证更复杂的数据,请添加自定义验证器。
根据预期数据使用授权字符白名单
使用非详细的响应拒绝无效的条目,以避免透露太多有关 API 操作及其验证的信息。
-
-
-
-
-
-