关于 attacking-graphql
面向 API 安全测试人员的 GraphQL 测试技能,指导在授权范围内恢复 Schema 并检查解析器级访问控制。重点涵盖批处理、注入和查询形状带来的风险。
主要功能
- 01通过内省及其他途径恢复 Schema
- 02按字段和解析器测试授权
- 03检查别名与批处理滥用
- 04评估查询深度和复杂度风险
- 05测试注入、CSRF 与订阅授权
适用场景
- 01在缺少 Schema 时梳理 GraphQL API
- 02检查跨用户或租户的字段访问
- 03评估登录接口的别名和批处理限制
- 04以有界查询验证拒绝服务风险
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trilwu/ secskills --skill attacking-graphql安装后在 Claude Code 中输入/attacking-graphql确认已加载。
详细介绍
攻击 GraphQL
GraphQL 将授权从路由转移到解析器,但大多数团队没有相应地迁移访问控制。REST 中保护 /admin/* 的习惯,在所有操作都到达同一个端点时并无对应做法——因此反复出现的问题不是罕见的 GraphQL 漏洞,而是某个无人想到要保护的字段上发生了普通的授权失效。
适用场景
- 目标暴露了
/graphql、/graphiql、/v1/graphql、/api/graphql - 请求正文包含
query、mutation、subscription或operationName - 响应采用
{"data": ..., "errors": [...]}格式 - 移动端或 SPA 客户端向后端发送 GraphQL 请求
- 你需要梳理一个没有其架构定义的 API
不适用场景
- REST 或 gRPC — 使用
testing-apis或attacking-grpc-protobuf - 有源代码可用 — 使用
auditing-code-for-vulnerabilities;直接阅读解析器会更快、更全面 - 围绕它构建的 Web 应用 — 使用
testing-web-applications - 针对生产环境发起拒绝服务攻击 — 评估风险,以最小规模验证,并在进行任何负载测试前取得明确授权
恢复架构定义
# Standard introspection
curl -s https://target/graphql -H 'Content-Type: application/json' \
-d '{"query":"query{__schema{types{name fields{name args{name type{name}}}}}}"}' | jq .
# Tooling that renders it usefully
graphql-cop -t https://target/graphql
clairvoyance https://target/graphql -o schema.json # works WITHOUT introspection
graphw00f -t https://target/graphql # fingerprint the engine
即使禁用了内省,通常仍可恢复架构定义。 大多数引擎在字段名称拼写错误时会返回“你是不是想找”建议,从而逐步泄露有效名称。clairvoyance 可以自动完成这项工作。
# Field suggestion leak — the response names fields you did not know
curl -s https://target/graphql -d '{"query":"{ userr { id } }"}' -H 'Content-Type: application/json'
# → "Cannot query field \"userr\" on type \"Query\". Did you mean \"user\"?"
其他架构定义来源:客户端包(查询通常内联在 JS 中)、意外对外提供的 .graphql 文件、持久化查询清单,以及公开的 Apollo Studio 或类似工具。
识别引擎 — 不同引擎的行为差异显著。Apollo、graphql-js、Hasura、graphene、gqlgen 和 HotChocolate 在批处理、建议、深度限制和错误详细程度方面各有不同的默认设置。graphw00f 可以识别引擎。
授权是重点
按字段和解析器测试授权,而不是按端点测试。常见的问题是:user(id:) 有保护,但 user { organization { members { email } } } 没有。
# 1. Object-level: request another tenant's or user's object by ID
query { user(id: "other-user-id") { id email phone } }
# 2. Field-level: the object is yours, but a field should not be exposed
query { me { id email passwordHash internalNotes stripeCustomerId } }
# 3. Traversal: reach a protected object through an unguarded edge
query { post(id: 1) { author { email resetToken orders { total } } } }
# 4. Mutations: the usual suspects, called directly
mutation { updateUser(id: "other", input: {role: ADMIN}) { id role } }
mutation { deleteAccount(id: "other") { success } }
通过关系进行遍历是最有价值的测试。 开发者会保护他们想到的入口;嵌套边继承父解析器所允许的权限,而这种权限往往什么也没有。枚举架构中的边,并从你合法拥有的任意对象出发,遍历到你无权访问的对象。
在你拥有的每个权限级别重复所有测试:匿名用户、低权限用户,以及另一个租户的用户。
批处理和别名滥用
单个 HTTP 请求可以包含多个操作,从而绕过按请求限速——这是绕过 2FA 和密码暴力破解防护的经典方式。
# Aliases: N attempts, one request
{
a1: login(user:"admin", pass:"1234") { token }
a2: login(user:"admin", pass:"1235") { token }
a3: login(user:"admin", pass:"1236") { token }
}
[ {"query":"{ user(id:1){email} }"},
{"query":"{ user(id:2){email} }"},
{"query":"{ user(id:3){email} }"} ]
多个引擎默认支持数组批处理。两种形式都要测试;它们通常受到不同的限制,而在 HTTP 层实施的限速无论如何都只会看到一个请求。
利用查询形状造成拒绝服务
# Depth: cyclic relationships nested repeatedly
{ user { posts { author { posts { author { posts { id } } } } } } }
# Breadth: aliases multiply one expensive resolver
{ a: search(q:"x"){id} b: search(q:"x"){id} c: search(q:"x"){id} ... }
# Field duplication amplifies without depth
{ user { id id id id id id ... } }
检查是否设置了:深度限制、复杂度/成本限制、超时、分页节点限制,以及引擎是否会批处理 N+1 个解析器调用,还是为每个节点发起一次查询。用小规模且有界的查询证明风险——测量不同深度下的响应时间变化,而不是实际耗尽服务。若一个 8 层查询耗时 30 秒,而 3 层查询耗时 30 毫秒,这就是证据;不需要让 API 停机。
通过解析器进行注入
解析器访问数据库的方式与其他处理程序相同,因此经典的漏洞类型同样适用——不同之处在于,参数是强类型的,而人们常误以为这就意味着经过了验证。
{ user(filter: "1' OR '1'='1") { id } } # SQL/NoSQL injection
{ users(where: {email: {_ilike: "%"}}) { email } } # Hasura-style filter abuse
{ file(path: "../../etc/passwd") { contents } } # traversal
Hasura 和类似的自动生成 API 尤其值得关注:它们会直接向客户端暴露丰富的 where 筛选条件,因此,宽松的行级安全配置意味着筛选语言本身就会成为漏洞。
值得执行的其他检查
- 通过
GET或表单编码的查询发起 CSRF。 如果端点接受?query=mutation{...}或application/x-www-form-urlencoded,则无需预检即可从跨源发起变更操作。 - 生产环境中的内省——单独来看严重性较低,但会泄露系统地图。
- 详细错误信息泄露堆栈跟踪、解析器名称和内部路径。
- 通过 WebSocket 进行订阅——通常只在连接时检查授权,此后不再检查,因此长时间运行的订阅在用户登出或权限变更后仍会持续。
- 持久化查询——检查客户端只发送哈希时,服务器是否仍接受任意查询;如果 APQ 实现在失败时会回退到任意查询,就无法提供保护。
- 通过 multipart 规范进行的文件上传,它使用独立的解析器。
应拒绝的合理化说法
- “已禁用内省,因此架构受到保护。” 建议字段会泄露架构信息,客户端打包内容中也包含架构。
- “网关会对端点进行速率限制。” 它统计的是 HTTP 请求。别名和批处理可以将一千个操作塞进一个请求。
- “参数有类型限制,因此不可能发生注入。” 类型限制的是结构,而不是内容。
String仍会传入数据库。 - “该字段只供管理 UI 使用。” 架构向所有人公开该字段;UI 不是访问控制机制。
- “顶层查询会检查授权。” 测试嵌套边。问题就出在这些地方。
- “已设置深度限制。” 也要检查广度和别名。
- “我会通过让系统宕机来证明 DoS。” 证明攻击趋势即可。进行负载测试前先征求同意。
ATT&CK 覆盖范围
根据 secskills-core/ttp-index.json 生成——编辑该文件,然后运行
python3 scripts/sync_attack.py --write。在报告中引用前,请对照
当前 ATT&CK 版本重新核实 ID。
初始访问(TA0001)
- T1190 利用面向公众的应用程序——另请参阅
testing-web-applications、testing-apis、enumerating-network-services、attacking-grpc-protobuf、exploiting-deserialization、exploiting-ssrf、exploiting-xxe
适用于上述任一项的检测内容:engineering-detections。主动搜索:hunting-threats。入侵后处置:responding-to-incidents。
参考资料
testing-apis——本文所专门针对的通用 API 方法auditing-code-for-vulnerabilities——存在源代码时,对解析器层进行审查testing-web-applications——端点所在的应用reporting-security-findings——授权问题的严重性评定- graphw00f、clairvoyance、graphql-cop、InQL (Burp)、Altair/GraphiQL