测试 GraphQL
ID |
---|
WSTG-APIT-01 |
总结
GraphQL 在现代 API 中已变得非常流行。它提供了简单性和嵌套对象,这有助于加快开发速度。虽然每种技术都有其优势,但它也可能使应用程序面临新的攻击面。此方案的目的是在使用 GraphQL 的应用程序上提供一些常见的错误配置和攻击向量。一些向量是 GraphQL 独有的(例如内省查询),有些是 API 的通用向量(例如 SQL 注入)。
本节中的示例将基于易受攻击的 GraphQL 应用程序 poc-graphql,该应用程序在 localhost:8080/GraphQL
映射为易受攻击的 GraphQL 节点的 Docker 容器中运行。
测试目标
- 评估是否部署了安全且生产就绪的配置。
- 验证所有输入字段是否针对通用攻击。
- 确保应用适当的访问控制。
如何测试
测试 GraphQL 节点与测试其他 API 技术没有太大区别。请考虑以下步骤:
ntrospection 查询
自省查询是 GraphQL 允许您询问支持哪些查询、哪些数据类型可用以及在进行 GraphQL 部署测试时需要的更多详细信息的方法。
“向 GraphQL 架构询问有关它支持的查询的信息通常很有用。GraphQL 允许我们使用内省系统来做到这一点!
有几种方法可以提取此信息并可视化输出,如下所示。
使用原生 GraphQL 内省
最直接的方法是发送带有以下有效负载的 HTTP 请求(使用个人代理),摘自 Medium上的一篇文章:
query IntrospectionQuery {
__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
}
}
}
}
}
}
}
}
结果通常很长(因此在此处进行了缩短),并且它将包含 GraphQL 部署的整个架构。
响应:
{
"data": {
"__schema": {
"queryType": {
"name": "Query"
},
"mutationType": {
"name": "Mutation"
},
"subscriptionType": {
"name": "Subscription"
},
"types": [
{
"kind": "ENUM",
"name": "__TypeKind",
"description": "An enum describing what kind of type a given __Type is",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "SCALAR",
"description": "Indicates this type is a scalar.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "OBJECT",
"description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "INTERFACE",
"description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "UNION",
"description": "Indicates this type is a union. `possibleTypes` is a valid field.",
"isDeprecated": false,
"deprecationReason": null
},
],
"possibleTypes": null
}
]
}
}
}
GraphQL Voyager 等工具可用于更好地了解 GraphQL 终端节点:
Figure 12.1-1: GraphQL Voyager
此工具创建 GraphQL 架构的实体关系图 (ERD) 表示形式,使您能够更好地了解正在测试的系统的运动部分。例如,从绘图中提取信息允许您查看可以查询 Dog 表。它还显示了 Dog 具有哪些属性:
- ID
- name
- veterinary (ID)
使用此方法有一个缺点:GraphQL Voyager 不会显示可以使用 GraphQL 完成的所有操作。例如,上图中未列出可用的突变。更好的策略是同时使用 Voyager 和下面列出的方法之一。
使用 GraphiQL
GraphiQL 是一个基于 Web 的 GraphQL IDE。它是 GraphQL 项目的一部分,主要用于调试或开发目的。最佳做法是不允许用户在生产部署中访问它。如果您正在测试暂存环境,则可能有权访问它,因此可以在使用内省查询时节省一些时间(当然,您可以在 GraphiQL 界面中使用内省)。
GraphiQL 有一个文档部分,该部分使用架构中的数据来创建正在使用的 GraphQL 实例的文档。本文档包含数据类型、更改以及基本上可以使用内省提取的所有信息。
使用 GraphQL Playground
GraphQL Playground
是一个 GraphQL 客户端。它可用于测试不同的查询,以及将 GraphQL IDE 划分为不同的 Playground,并按主题或为它们分配名称对其进行分组。与 GraphiQL 非常相似,Playground 可以为您创建文档,而无需手动发送自省查询和处理响应。它还有另一个很大的优势:它不需要 GraphiQL 界面即可使用。您可以通过 URL 将该工具定向到 GraphQL 节点,也可以在本地将其与数据文件一起使用。GraphQL Playground 可用于直接测试漏洞,因此您无需使用个人代理发送 HTTP 请求。这意味着您可以使用此工具与 GraphQL 进行简单的交互和评估。对于其他更高级的负载,请使用 Personal Proxy。
请注意,在某些情况下,您需要在底部设置 HTTP 标头,以包含会话 ID 或其他身份验证机制。这仍然允许创建具有不同权限的多个 “IDE” 来验证是否确实存在授权问题。
Figure 12.1-2: GraphQL Playground High Level API Docs
Figure 12.1-3: GraphQL Playground API Schema
您甚至可以下载架构以在 Voyager 中使用。
内省结论
Introspection 是一个有用的工具,它允许用户获取有关 GraphQL 部署的更多信息。但是,这也将允许恶意用户访问相同的信息。最佳实践是限制对内省查询的访问,因为如果完全禁用此功能,某些工具或请求可能会失败。由于 GraphQL 通常桥接到系统的后端 API,因此最好实施严格的访问控制。
授权
Introspection 是查找授权问题的第一个位置。如前所述,应限制对内省的访问,因为它允许数据提取和数据收集。一旦测试人员可以访问架构并了解要提取的敏感信息,他们就应该发送不会因权限不足而被阻止的查询。默认情况下,GraphQL 不强制实施权限,因此由应用程序来执行授权强制执行。
在前面的示例中,自省查询的输出显示有一个名为 auth
的查询。这似乎是提取敏感信息(如 API 令牌、密码等)的好地方。
Figure 12.1-4: GraphQL 身份验证查询 API
测试授权实现因部署而异,因为每个架构都有不同的敏感信息,因此需要关注的目标也不同。
在这个易受攻击的示例中,每个用户(即使是未经身份验证的)都可以访问数据库中列出的每个兽医的身份验证令牌。这些令牌可用于执行架构允许的其他操作,例如使用突变将狗与任何指定的兽医关联或取消关联,即使请求中没有与兽医匹配的身份验证令牌也是如此。
下面是一个示例,其中测试人员使用他们不拥有的提取令牌以兽医 “Benoit” 的身份执行操作:
query brokenAccessControl {
myInfo(accessToken:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNjAzMjkxMDE2fQ.r3r0hRX_t7YLiZ2c2NronQ0eJp8fSs-sOUpLyK844ew", veterinaryId: 2){
id, name, dogs {
name
}
}
}
And the response:
{
"data": {
"myInfo": {
"id": 2,
"name": "Benoit",
"dogs": [
{
"name": "Babou"
},
{
"name": "Baboune"
},
{
"name": "Babylon"
},
{
"name": "..."
}
]
}
}
}
列表中的所有 Dog 都属于 Benoit,而不是 auth token 所有者。如果未实施适当的授权实施,则可以执行此类操作。
注入
GraphQL 是应用程序 API 层的实现,因此,它通常将请求直接转发到后端 API 或数据库。这允许您利用任何潜在的漏洞,例如 SQL 注入、命令注入、跨站点脚本等。使用 GraphQL 只会改变恶意负载的入口点。
您可以参考 OWASP 测试指南中的其他场景来获得一些想法。
GraphQL 还具有标量,通常用于没有本机数据类型的自定义数据类型,例如 DateTime。这些类型的数据没有现成的验证,因此非常适合进行测试。
SQL 注入
示例应用程序在查询 dogs(namePrefix: String, limit: Int = 500): [Dog!]
中很容易受到攻击,因为namePrefix
参数在 SQL 查询中是串联的。连接用户输入是应用程序的常见不当做法,可能会使应用程序面临 SQL 注入。
以下查询从数据库内的 CONFIG
表中提取信息:
query sqli {
dogs(namePrefix: "ab%' UNION ALL SELECT 50 AS ID, C.CFGVALUE AS NAME, NULL AS VETERINARY_ID FROM CONFIG C LIMIT ? -- ", limit: 1000) {
id
name
}
}
对此查询的响应为:
{
"data": {
"dogs": [
{
"id": 1,
"name": "Abi"
},
{
"id": 2,
"name": "Abime"
},
{
"id": 3,
"name": "..."
},
{
"id": 50,
"name": "$Nf!S?(.}DtV2~:Txw6:?;D!M+Z34^"
}
]
}
}
该查询包含在示例应用程序中对 JWT 进行签名的密钥,这是非常敏感的信息。
为了知道在任何特定应用程序中要查找的内容,收集有关应用程序如何构建和数据库表如何组织的信息将很有帮助。您还可以使用 sqlmap
等工具来查找注入路径,甚至自动从数据库中提取数据。
跨站点脚本 (XSS)
当攻击者注入随后由浏览器运行的可执行代码时,就会发生跨站点脚本。在 Input Validation一章中了解 XSS 测试。您可以使用测试反射式跨站点脚本中的有效负载来测试反射型 XSS。
在此示例中,错误可能会反映输入,并可能导致 XSS 发生。
有效载荷:
query xss {
myInfo(veterinaryId:"<script>alert('1')</script>" ,accessToken:"<script>alert('1')</script>") {
id
name
}
}
Response:
{
"data": null,
"errors": [
{
"message": "Validation error of type WrongType: argument 'veterinaryId' with value 'StringValue{value='<script>alert('1')</script>'}' is not a valid 'Int' @ 'myInfo'",
"locations": [
{
"line": 2,
"column": 10,
"sourceName": null
}
],
"description": "argument 'veterinaryId' with value 'StringValue{value='<script>alert('1')</script>'}' is not a valid 'Int'",
"validationErrorType": "WrongType",
"queryPath": [
"myInfo"
],
"errorType": "ValidationError",
"extensions": null,
"path": null
}
]
}
拒绝服务 (DoS) 查询
GraphQL 提供了一个非常简单的接口,允许开发人员使用嵌套查询和嵌套对象。此功能也可以以恶意方式使用,通过调用类似于递归函数的深度嵌套查询,并通过耗尽 CPU、内存或其他计算资源来导致拒绝服务。
回顾一下图 12.1-1,你可以看到可以创建一个 Dog 对象包含 Veterinary 对象的循环。可能会有无数的嵌套对象。
这允许进行深度查询,这可能会使应用程序过载:
query dos {
allDogs(onlyFree: false, limit: 1000000) {
id
name
veterinary {
id
name
dogs {
id
name
veterinary {
id
name
dogs {
id
name
veterinary {
id
name
dogs {
id
name
veterinary {
id
name
dogs {
id
name
veterinary {
id
name
dogs {
id
name
}
}
}
}
}
}
}
}
}
}
}
}
可以实施多种安全措施来防止这些类型的查询,如 Remediation (补救) 部分列出。滥用查询可能会导致 GraphQL 部署的 DoS 等问题,应包含在测试中。
批处理攻击
GraphQL 支持将多个查询批处理到单个请求中。这允许用户有效地请求多个对象或对象的多个实例。但是,攻击者可以利用此功能来执行批处理攻击。在一个请求中发送多个查询如下所示:
[
{
query: < query 0 >,
variables: < variables for query 0 >,
},
{
query: < query 1 >,
variables: < variables for query 1 >,
},
{
query: < query n >
variables: < variables for query n >,
}
]
在示例应用程序中,可以发送单个请求,以便使用可猜测的 ID(它是一个递增的整数)提取所有兽医名称。然后,攻击者可以利用这些名称来获取访问令牌。这些请求可能会被批处理,而不是在许多请求中这样做,这些请求可能会被网络安全措施(如 Web 应用程序防火墙或速率限制器(如 Nginx)阻止)。这意味着只有几个请求,这可能允许在不被检测到的情况下进行有效的暴力破解。下面是一个示例查询:
query {
Veterinary(id: "1") {
name
}
second:Veterinary(id: "2") {
name
}
third:Veterinary(id: "3") {
name
}
}
这将向攻击者提供兽医的名称,如前面所示,这些名称可用于批量多次查询请求这些兽医的授权令牌。
例如:
query {
auth(veterinaryName: "Julien")
second: auth(veterinaryName:"Benoit")
}
批处理攻击可用于绕过网站上实施的许多安全措施。它还可用于枚举对象并尝试暴力破解多重身份验证或其他敏感信息。
详细的错误消息
GraphQL 在运行时可能会遇到意外错误。发生此类错误时,服务器可能会发送错误响应,该响应可能会泄露内部错误详细信息或应用程序配置或数据。这允许恶意用户获取有关应用程序的更多信息。作为测试的一部分,应通过发送意外数据来检查错误消息,此过程称为模糊测试。应搜索回复中可能使用此技术泄露的潜在敏感信息。
底层 API 的公开
GraphQL 是一项相对较新的技术,一些应用程序正在从旧 API 过渡到 GraphQL。在许多情况下,GraphQL 部署为标准 API,它将请求(使用 GraphQL 语法发送)以及响应转换为底层 API。如果未正确检查对底层 API 的请求以进行授权,则可能导致权限升级。
例如,包含参数 id=1/delete
的请求可能被解释为/api/users/1/delete
。这可以扩展到对属于user=1
的其他资源的操纵。还有一种可能是,请求被解释为对 GraphOL节点的授权,而不是对真实请求者的授权。
测试人员应尝试访问底层 API 方法,因为可能会提升权限。
修复
- 限制对 Introspection 查询的访问。
- 实施输入验证。
- GraphQL 没有验证输入的原生方法,但是,有一个名为 “graphql-constraint-directive” 的开源项目,它允许将输入验证作为架构定义的一部分。
- 单独的输入验证是有帮助的,但它并不是一个完整的解决方案,应该采取额外的措施来缓解注入攻击。
- 实施安全措施以防止滥用查询。
- Timeouts:限制允许查询运行的时间。
- 最大查询深度:限制允许的查询深度,这可以防止过深的查询滥用资源。
- 设置最大查询复杂性:限制查询的复杂性以减少 GraphQL 资源的滥用。
- 使用基于服务器时间的限制:限制用户可以消耗的服务器时间量。
- 使用基于查询复杂性的限制:限制用户可以使用的查询的总复杂性。
- 发送一般错误消息:使用不透露部署详细信息的一般错误消息。
- 缓解批处理攻击:
- 在代码中添加对象请求速率限制。
- 防止对敏感对象进行批处理。
- 限制一次可以运行的查询数。
有关修复 GraphQL 弱点的更多信息,请参阅 GraphQL 备忘单。
工具
- GraphQL Playground
- GraphQL Voyager
- sqlmap
- InQL (Burp Extension)
- GraphQL Raider (Burp Extension)
- GraphQL (Add-on for ZAP)