我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

AI 渗透测试引擎在 GraphQL 订阅中发现严重的 WebSocket BFLA

Ostorlab 的 AI 渗透测试引擎系统性地发现了一个 GraphQL WebSocket 端点中严重的函数级授权失效(BFLA)漏洞,该漏洞允许在未经身份验证的情况下访问一项实时翻译服务。本案例研究详细介绍了该 AI 从发现到概念验证的逐步过程。

引言

现代 Web 应用越来越依赖 WebSocket 等实时通信协议来提供动态的用户体验。当它与 GraphQL 结合时,这一组合可以通过订阅(subscription)实现强大而高效的数据交换。然而,这种复杂性也引入了新的攻击面,尤其是在授权方面。保护 GraphQL 订阅需要在多个阶段进行严格检查:连接、订阅发起以及逐事件的数据过滤。

Ostorlab 的 AI 渗透测试引擎正是为应对这种复杂性而设计的。它能够系统性地识别并验证手动测试可能遗漏的授权缺口。本文展示了该 AI 引擎如何在一个 GraphQL WebSocket 端点中发现一个严重的函数级授权失效(BFLA)漏洞,该漏洞允许任何未经身份验证的用户执行订阅并接收敏感数据。

Ostorlab AI 渗透测试:GraphQL WebSocket BFLA

该引擎被赋予了一个高层目标:验证目标应用 REDACTED 上的 WebSocket 订阅授权。随之而来的是一个有条不紊、循序渐进的发现、探测与确认过程。

第 1 步:测试未经身份验证的 GraphQL WebSocket 连接

该 AI 的第一个动作是测试最基本的行为:WebSocket 端点是否会在没有任何凭据的情况下接受连接?

对 /subscriptions 进行基线的未认证 WS 握手。目标:验证 GraphQL WebSocket 服务器是否会在没有凭据的情况下接受连接。

执行摘要: 该引擎使用 graphql-transport-ws 子协议连接到 wss://REDACTED/subscriptions。它发送了一个带有空载荷的标准 connection_init 帧,未提供任何 Authorization 令牌。

结果: 服务器以 {"type":"connection_ack"} 作出响应。事实上,它发送了两个 ack 帧,这是 AI 注意到的一个异常。

结论: 未经身份验证的握手被接受。服务器在连接阶段不要求身份验证。这是潜在弱点的第一个信号。

第 2 步:在未经身份验证的情况下对 GraphQL 订阅架构进行自省(Introspection)

连接建立后,该 AI 下一个合乎逻辑的步骤是确定有哪些操作可用。它尝试在未经身份验证的 WebSocket 连接上执行 GraphQL 自省查询。

尝试在 WebSocket 上进行一次未认证的 GraphQL 自省,以枚举订阅操作并验证是否允许未经身份验证的操作。

执行摘要: 该引擎发送了一个 subscribe 帧,其中包含一个聚焦于 Subscription 类型的自省查询。 {"id":"1","type":"subscribe","payload":{"query":"query { __schema { subscriptionType { name fields { name } } } }","variables":{}}}

结果: 服务器返回了一个包含架构数据的 next 帧,随后是一个 complete 帧。

{
  "id": "1",
  "type": "next",
  "payload": {
    "data": {
      "__schema": {
        "subscriptionType": {
          "name": "Subscription",
          "fields": [
            { "name": "generateSamplePromptResult" },
            { "name": "translateContent" },
            { "name": "upsertRewardSubscription" }
          ]
        }
      }
    }
  }
}

结论: 服务器允许在 WebSocket 上进行未经身份验证的自省,这是一个严重的信息泄露。它揭示了三个订阅操作,为该引擎的授权测试提供了明确的目标。

第 3 步:针对 BFLA 测试 translateContent 订阅

该引擎选择 translateContent 作为它的第一个直接测试对象。在对其输入架构进行自省后,它构造了一个语法上有效但无害的订阅请求。

尝试以一个最小的有效输入对 translateContent 进行一次未认证的订阅,以验证未经身份验证的订阅是否会被处理。

执行摘要: 该引擎为 translateContent 发送了一个 subscribe 帧,提供了一个最小载荷,并仅请求 __typename 以确认执行情况。 {"id":"1","type":"subscribe","payload":{"query":"subscription($data: ContentTranslationSubscriptionInput!){ translateContent(data:$data){ __typename } }","variables":{"data":{"targetLanguage":"en","sourceContent":"hello"}}}}

结果: 服务器接受了该订阅,并返回了两个 next 帧,随后是 complete。

{"id":"1","type":"next","payload":{"data":{"translateContent":{"__typename":"TranslateContentResponse"}}}}
{"id":"1","type":"next","payload":{"data":{"translateContent":{"__typename":"TranslateContentResponse"}}}}
{"id":"1","type":"complete"}

结论: translateContent 订阅在没有任何身份验证的情况下被成功处理。这已经超越了信息泄露,进入了对后端函数主动的、未经身份验证的执行。

第 4 步:通过 GraphQL WebSocket BFLA 验证数据暴露

收到一个 __typename 证明了该操作已运行,但它是否会返回真实数据呢?该 AI 的最后一步是请求具体的字段以确认数据暴露。

通过从 translateContent 请求实际字段来确认未经身份验证的数据返回。

执行摘要: 该引擎重复了该订阅,这一次请求 content、isFinal 和 subscriptionId 字段。 {"id":"1","type":"subscribe","payload":{"query":"subscription($data: ContentTranslationSubscription-Input!){ translateContent(data:$data){ content isFinal subscriptionId } }","variables":{"data":{"targetLanguage":"en","sourceContent":"hello"}}}}

结果: 服务器返回了包含翻译后文本的 next 帧。

{
  "id": "1",
  "type": "next",
  "payload": {
    "data": {
      "translateContent": {
        "content": "Hello",
        "isFinal": false,
        "subscriptionId": "yrpsrqkv7li34w23r25dx4b2"
      }
    }
  }
}
{
  "id": "1",
  "type": "next",
  "payload": {
    "data": {
      "translateContent": {
        "content": "Hello",
        "isFinal": true,
        "subscriptionId": "yrpsrqkv7li34w23r25dx4b2"
      }
    }
  }
}

结论: 这是函数级授权失效(BFLA)漏洞的确凿证据。未经身份验证的攻击者可以执行 translateContent 函数并接收其结果。

第 5 步:识别 GraphQL 订阅中不一致的授权

为了解该问题的范围,该引擎还测试了另一个发现的订阅 generateSamplePromptResult。

尝试对 generateSamplePromptResult 进行一次未认证的订阅,以评估逐操作的授权情况。

执行摘要: 该引擎为 generateSamplePromptResult 发送了一个有效的、未经身份验证的订阅请求。

结果: 服务器立即以代码 4500 关闭了连接,原因为:"You are not authorized to perform this action"。

结论: 该操作正确地强制执行了授权。这一发现至关重要,因为它指向不一致的安全控制;这是一种常见的反模式,即开发者保护了某些端点,却遗漏了另一些。

最终报告:Ostorlab 关于 GraphQL WebSocket BFLA 的发现

1. 执行摘要

Ostorlab AI 渗透测试引擎在位于 wss://REDACTED/subscriptions 的 GraphQL WebSocket 端点中发现了一个严重的函数级授权失效(BFLA)漏洞。translateContent 订阅操作被公开暴露,允许任何未经身份验证的用户执行它并接收流式返回的结果。这构成了直接的权限绕过,并可能导致资源滥用和潜在的数据暴露。该引擎还注意到授权不一致,因为同一端点上的其他订阅正确地强制执行了访问控制。

2. 方法论

该 AI 引擎遵循一套系统化的方法来测试 WebSocket 授权: 1. 端点发现: 识别出 GraphQL WebSocket 端点及其协议(graphql-transport-ws)。 2. 未认证基线: 确认服务器接受了一个未经身份验证的 connection_init 握手。 3. 架构枚举: 利用未经身份验证的 GraphQL 自省来发现可用的订阅操作。 4. 逐操作测试: 遍历发现的订阅(translateContent、generateSamplePromptResult),尝试在未经身份验证的情况下执行它们。 5. 证据确认: 通过请求并接收具体的数据字段(而不仅仅是状态消息)来确认数据泄露。

3. 发现

  • translateContent 订阅中的 BFLA: 主要发现是 translateContent 订阅缺乏任何授权检查。攻击者可以在未经身份验证的情况下连接、订阅并接收翻译结果。这一点通过发送一个有效载荷并收到翻译后的文本作为响应得到了确认。该漏洞还进一步得到证实:即使是一个明确无效的令牌也会被忽略。
  • 通过未经身份验证的自省造成的信息泄露: 订阅的 GraphQL 架构可在 WebSocket 上被公开访问,使攻击者能够轻松发现可用的操作及其所需的输入。
  • 不一致的授权控制: 虽然 translateContent 存在漏洞,但同一端点上的 generateSamplePromptResult 订阅却正确地拒绝了未经身份验证的请求。这种不一致表明,存在漏洞的解析器(resolver)上缺少了某个注解或安全控制。

4. 修复

为应对这些发现,我们建议如下: 1. 默认强制身份验证: 实施默认拒绝(default-deny)策略。所有 GraphQL 解析器,尤其是订阅相关的解析器,除非被明确标记为公开,否则都应要求有效的、已通过身份验证的会话。 2. 集中授权逻辑: 使用中间件或框架级钩子,在所有 WebSocket 操作上一致地强制执行身份验证和授权检查,既在连接时执行,也在每一个订阅请求上执行。 3. 在生产环境中禁用自省: 虽然自省对开发很有用,但在可公开访问的端点上应将其禁用,以防止攻击者轻松地绘制出 API 攻击面。

5. 结论

保护现代的实时 API 需要一种纵深防御的方法,使授权得到一致的应用。本案例研究展示了 Ostorlab 的 AI 渗透测试引擎如何能够在复杂的 WebSocket 实现中系统性地发现诸如 BFLA 和不一致安全控制这类细微但严重的缺陷。通过模拟攻击者从发现到利用的逻辑推进过程,该引擎提供了确凿、可付诸行动的证据,帮助开发者构建更安全的应用。

标签:

security, ai, poc, pentest, graphql