GraphQL
简介
GraphQL被强调为REST API的高效替代方案,为从后端查询数据提供了简化的方法。与REST不同,后者通常需要跨多个端点发出多个请求以收集数据,GraphQL使得通过单个请求获取所有所需信息成为可能。这种简化显著地有利于开发人员,减少了数据获取过程的复杂性。
GraphQL与安全
随着包括GraphQL在内的新技术的出现,也出现了新的安全漏洞。需要注意的一个关键点是GraphQL默认不包含身份验证机制。开发人员有责任实施这些安全措施。没有适当的身份验证,GraphQL端点可能会向未经身份验证的用户暴露敏感信息,构成重大安全风险。
目录暴力攻击和GraphQL
为了识别暴露的GraphQL实例,建议在目录暴力攻击中包含特定路径。这些路径包括:
/graphql
/graphiql
/graphql.php
/graphql/console
/api
/api/graphql
/graphql/api
/graphql/graphql
识别开放的GraphQL实例允许检查支持的查询。这对于了解通过端点访问的数据至关重要。GraphQL的内省系统通过详细说明模式支持的查询来实现这一点。有关更多信息,请参考GraphQL关于内省的文档:GraphQL:用于API的查询语言。
指纹
工具graphw00f能够检测服务器中使用的GraphQL引擎,然后为安全审计人员提供一些有用信息。
通用查询
要检查URL是否是GraphQL服务,可以发送一个通用查询 query{__typename}
。如果响应包含 {"data": {"__typename": "Query"}}
,则确认该URL托管了一个GraphQL端点。此方法依赖于GraphQL的__typename
字段,该字段显示了查询对象的类型。
基本枚举
GraphQL通常支持GET,POST(x-www-form-urlencoded)和POST(json)。尽管出于安全考虑,建议仅允许json以防止CSRF攻击。
自省
要使用自省来发现模式信息,请查询__schema
字段。此字段可在所有查询的根类型上使用。
使用此查询,您将找到正在使用的所有类型的名称:
使用此查询,您可以提取所有类型、字段和参数(以及参数的类型)。这将非常有用,以了解如何查询数据库。
错误
了解错误是否会显示是很有趣的,因为它们将提供有用的信息。
通过内省枚举数据库模式
如果启用了内省但上述查询无法运行,请尝试从查询结构中删除onOperation
、onFragment
和onField
指令。
内联反射查询:
最后一行代码是一个GraphQL查询,将从GraphQL中转储所有元信息(对象名称、参数、类型...)
如果启用了内省,您可以使用GraphQL Voyager在GUI中查看所有选项。
查询
现在我们知道数据库中保存了哪种信息,让我们尝试提取一些值。
在内省中,您可以找到可以直接查询的对象(因为您不能仅因为存在而查询对象)。在下图中,您可以看到"queryType"称为"Query",而"Query"对象的一个字段是"flags",它也是一个对象类型。因此,您可以查询flag对象。
请注意,查询"flags"的类型是"Flags",并且此对象定义如下:
您可以看到"Flags"对象由name和value组成,然后您可以使用以下查询获取所有标志的名称和值:
请注意,如果要查询的对象是像以下示例中的字符串这样的基本类型
您可以直接查询它:
在另一个示例中,"Query" 类型对象内有 2 个对象:"user" 和 "users"。 如果这些对象不需要任何参数来搜索,只需请求所需的数据,就可以检索所有信息。在这个示例中,你可以提取已保存的用户名和密码:
然而,在这个示例中,如果你尝试这样做,你会收到这个错误:
看起来它会使用类型为 Int 的 "uid" 参数进行搜索。
无论如何,我们已经知道,在基本枚举部分提出了一个查询,显示了我们所需的所有信息:query={__schema{types{name,fields{name, args{name,description,type{name, kind,ofType{name, kind}}}}}}}
如果你阅读了我运行该查询时提供的图像,你会看到 "user" 具有类型为 Int 的 arg "uid"。
因此,通过进行一些轻量级的 uid 暴力破解,我发现在 uid=1 时检索到了一个用户名和一个密码:
query={user(uid:1){user,password}}
请注意,我发现我可以请求 "user" 和 "password" 这些参数,因为如果我尝试查找不存在的内容 (query={user(uid:1){noExists}}
),我会收到这个错误:
在枚举阶段期间,我发现 "dbuser" 对象的字段是 "user" 和 "password。
查询字符串转储技巧(感谢 @BinaryShadow_)
如果你可以按字符串类型搜索,比如:query={theusers(description: ""){username,password}}
,并且你搜索一个空字符串,它将转储所有数据。(请注意,此示例与教程示例无关,假设你可以使用 "theusers" 搜索名为 "description" 的 String 字段).
搜索
在这个设置中,一个数据库包含人员和电影。人员由他们的电子邮件和姓名标识;电影由它们的名称和评分标识。人员可以互为朋友,也可以拥有电影,表示数据库中的关系。
你可以通过姓名搜索人员并获取他们的电子邮件:
您可以通过姓名搜索人员并获取他们订阅的电影:
请注意如何指示检索个人的subscribedMovies
的name
。
您还可以同时搜索多个对象。在这种情况下,搜索了2部电影:
甚至使用别名关联多个不同对象的关系:
Mutations
变异用于在服务器端进行更改。
在内省中,您可以找到声明的变异。在下图中,"MutationType" 被称为 "Mutation","Mutation" 对象包含变异的名称(在本例中为 "addPerson"):
在此设置中,数据库包含人员和电影。人员由其电子邮件和姓名标识;电影由其名称和评级标识。人员可以彼此成为朋友,并且还可以拥有电影,表示数据库中的关系。
用于在数据库中创建新电影的变异可以如下(在此示例中,变异称为 addMovie
):
注意查询中指示了数据的值和类型。
此外,数据库支持一个名为 addPerson
的 mutation 操作,允许创建人员以及将它们与现有的朋友和电影关联起来。重要的是要注意,必须在将它们链接到新创建的人员之前,朋友和电影必须在数据库中预先存在。
指令过载
如报告中描述的一个漏洞所述,指令过载意味着调用一个指令甚至数百万次,使服务器浪费操作,直到可能导致拒绝服务攻击。
在1个 API 请求中批量暴力破解
这些信息来自https://lab.wallarm.com/graphql-batching-attack/。 通过 GraphQL API 进行身份验证,同时发送多个带有不同凭据的查询以进行检查。这是一种经典的暴力破解攻击,但现在由于 GraphQL 的批处理功能,可以在一个 HTTP 请求中发送多个登录/密码对。这种方法会欺骗外部速率监控应用程序,让其认为一切正常,没有暴力破解机器人试图猜测密码。
下面是一个应用程序身份验证请求的最简单演示,每次同时使用 3 对不同的电子邮件/密码。显然,可以以相同的方式在单个请求中发送数千个:
从响应截图中可以看到,第一个和第三个请求返回了 null 并在 error 部分反映了相应的信息。第二个变异具有正确的身份验证数据,响应中包含了正确的身份验证会话令牌。
无需内省的 GraphQL
越来越多的 GraphQL 端点禁用了内省。然而,当收到意外请求时,GraphQL 抛出的错误足以让像 clairvoyance 这样的工具重新创建大部分模式。
此外,Burp Suite 扩展程序 GraphQuail 观察通过 Burp 传递的 GraphQL API 请求,并且 构建 一个内部 GraphQL 模式,每次看到新查询时都会这样做。它还可以为 GraphiQL 和 Voyager 暴露模式。当接收到内省查询时,该扩展程序返回一个虚假响应。因此,GraphQuail 显示了 API 中可用于使用的所有查询、参数和字段。有关更多信息,请查看 此处。
一个不错的 单词列表,用于发现 GraphQL 实体可以在此处找到。
绕过 GraphQL 内省防御
绕过 GraphQL 内省防御
为了绕过 API 中内省查询的限制,在 __schema
关键字后插入一个 特殊字符 是有效的。这种方法利用了常见的开发人员在正则表达式模式中的疏忽,这些模式旨在通过关注 __schema
关键字来阻止内省。通过添加像 空格、换行符和逗号 这样的字符,GraphQL 会忽略但正则表达式可能没有考虑到的字符,可以规避限制。例如,一个在 __schema
后面加上换行符的内省查询可能会绕过这种防御:
如果不成功,考虑使用GET请求或带有 x-www-form-urlencoded
的POST等替代请求方法,因为限制可能仅适用于POST请求。
发现暴露的GraphQL结构
当禁用内省时,检查网站源代码中JavaScript库中预加载查询是一种有用的策略。可以使用开发者工具中的Sources
选项卡找到这些查询,从而深入了解API的模式并揭示可能暴露的敏感查询。在开发者工具中搜索的命令为:
GraphQL中的CSRF
如果你不知道什么是CSRF,请阅读以下页面:
pageCSRF (Cross Site Request Forgery)在这里,你将能够找到一些GraphQL端点配置没有CSRF令牌。
请注意,GraphQL请求通常通过使用Content-Type **application/json
**的POST请求发送。
然而,大多数 GraphQL 端点也支持 form-urlencoded
POST 请求:
因此,由于类似之前的 CSRF 请求是无需预检请求发送的,因此可能利用 CSRF 在 GraphQL 中执行 更改。
但是,请注意 Chrome 的 samesite
标志的新默认 cookie 值为 Lax
。这意味着该 cookie 仅在第三方网站的 GET 请求中发送。
请注意,通常也可以将查询请求作为GET请求发送,而在 GET 请求中可能不会验证 CSRF 令牌。
此外,可能利用 XS-Search 攻击从 GraphQL 端点中滥用用户凭据来突破内容。
有关更多信息,请查看此处的原始帖子。
GraphQL 中的授权
端点上定义的许多 GraphQL 函数可能仅检查请求者的身份验证而不检查授权。
修改查询输入变量可能导致泄露敏感帐户详细信息leaked。
甚至可能通过修改其他帐户数据尝试接管帐户来导致突破。
绕过 GraphQL 授权
链接查询 可以绕过弱身份验证系统。
在下面的示例中,您可以看到操作是“forgotPassword”,它应该只执行与之关联的 forgotPassword 查询。这可以通过在末尾添加一个查询来绕过,例如我们添加了 "register" 和一个用户变量,系统将其注册为新用户。
使用 GraphQL 中的别名绕过速率限制
在 GraphQL 中,别名是一个强大的功能,允许在进行 API 请求时明确命名属性。这种能力特别适用于在单个请求中检索同一类型的多个实例。别名可用于克服阻止 GraphQL 对象具有相同名称的多个属性的限制。
建议查看有关 GraphQL 别名的详细理解的资源:别名。
虽然别名的主要目的是减少大量 API 调用的必要性,但已经发现了一个意外的用例,即可以利用别名来对 GraphQL 端点执行暴力攻击。这是可能的,因为一些端点受到速率限制器的保护,这些速率限制器旨在通过限制HTTP 请求的数量来阻止暴力攻击。然而,这些速率限制器可能不考虑每个请求中的操作数量。鉴于别名允许在单个 HTTP 请求中包含多个查询,它们可以规避此类速率限制措施。
考虑下面提供的示例,说明了如何使用别名查询来验证商店折扣代码的有效性。这种方法可以绕过速率限制,因为它将多个查询编译到一个 HTTP 请求中,从而可能允许同时验证多个折扣代码。
工具
漏洞扫描器
https://github.com/gsmith257-cyber/GraphCrawler:工具包,可用于抓取模式并搜索敏感数据,测试授权,暴力破解模式,并找到到给定类型的路径。
https://github.com/swisskyrepo/GraphQLmap:也可用作CLI客户端以自动化攻击。
https://gitlab.com/dee-see/graphql-path-enum:列出在GraphQL模式中到达给定类型的不同方式的工具。
https://github.com/doyensec/inql:用于高级GraphQL测试的Burp扩展。 Scanner 是InQL v5.0的核心,您可以分析GraphQL端点或本地内省模式文件。它会自动生成所有可能的查询和变异,并将它们组织成结构化视图供您分析。 Attacker 组件可让您运行批量GraphQL攻击,可用于规避实现不佳的速率限制。
客户端
自动化测试
解释AutoGraphQL的视频:https://www.youtube.com/watch?v=JJmufWfVvyU
参考资料
最后更新于