导语:GraphQL 用一种由 Facebook 开源的 API 查询语言,把 REST 一整套路由压缩成一个端点——查询、写入、订阅全部塞进同一条 POST。这把双刃剑的另一面也足够锋利:内省一开,等于把数据模型直接递到攻击者手里;再加上参数未过滤、批处理滥用、CSRF 防护缺失,整条攻击链几小时就能跑完。
一、GraphQL 是什么:单端点的双刃剑
传统 REST API 里,每个资源对应一个 URL,查询、写入、删除各走各的路径。GraphQL 换个思路:把后端整个数据模型抽象成一张“图”,客户端只用同一个端点(通常是 /graphql),发一条结构化查询就能精确取回所需字段——不多取,也不少取。
前端省去了为每个新字段等后端排期出接口的麻烦,但代价同样摆在明面上:同一个端点扛下了所有读写逻辑。攻击者只要能摸到一个 URL,就等于摸到了整个系统的入口。
GraphQL 文档中定义了三类操作:
- query:只读,行为上类似 GET。
- mutation:写入并返回结果,覆盖了 POST/PUT/DELETE 的职能。
- subscription:长连接推送,用于实时订阅。
攻击者往往偏爱 mutation——一旦认证被绕过,直接改数据比读数据更“值钱”。
数据模型通过 Schema Definition Language(SDL,模式定义语言)来定义,每个类型的字段、类型、是否可空都写在这一份声明里。SDL 看着像配置文件,实际上是后端逻辑的真值来源:前端、后端、文档、IDE 自动补全全都围着它转。
二、内省查询:把数据模型直接送人
内省(Introspection)是 GraphQL 内建的元数据查询能力。任何 GraphQL 端点都暴露了三个特殊字段:
__schema:返回整个 schema 的结构
__type:返回某个具体类型的字段定义
__typename:返回被查询对象的类型名(字符串)
这套机制最初是为客户端自动补全和文档生成服务的,但攻击者拿它来绘制整张后端地图。
2.1 探测端点是否存在
先用一条最小查询确认目标是不是 GraphQL:
{ __typename }
所有 GraphQL 端点都会返回 {"data": {"__typename": "Query"}}。如果对方回 404,说明不是 GraphQL;如果回的是别的 JSON 结构,协议也对不上。
常见端点路径有这么几条:/graphql、/api、/api/graphql、/graphql/api、/graphql/graphql。PortSwigger 文档建议五条挨个试一遍,再叠上 /v1 后缀。
内省如果开着,下面的查询能一口气把整个 schema 拖出来:
{
__schema {
queryType { fields { name description } }
mutationType { fields { name description } }
types { name description }
}
}
返回结果中包含每个 query、mutation、subscription 的字段名和注释。实战中不少团队把字段描述写成“删除用户”“重置密码”这类直白文字,相当于把攻击指南直接挂在 API 上。Hasura 教程给过更精简的版本:只想找 mutation 就只查 mutationType。
HackerOne 报告 #291531 就是一条典型路径:访问 /graphql,运行内省,拿到包含 PII(个人可识别信息)字段的完整 schema。报告 #1132803 中,running.com/en-in/graphql 一次拉出了所有查询、类型、字段和可用操作。HackerOne 官方博客在 2024 年 7 月还专门写过一篇:第三方子域的 GraphQL 端点开了内省,主站促销横幅系统的能力列表被全部拖出——攻击者顺势拿到 mutation 入口,绕过身份验证。
三、八大攻击面与四步探测法
3.1 攻击面全景

day20 原文列出了八类 GraphQL 攻击,可以归到三个维度:
认证授权类
协议设计类
- 内省查询开启(上面刚讲过)
- Debug 模式开启(堆栈、源码、查询日志外泄)
- 应用层 DoS(嵌套查询、字段别名、批处理把数据库打爆)
注入与泄漏类
- SQL 注入(参数拼进 ORM)
- 错误信息数据泄漏(报错里带回 PII)
3.2 探测四步法
PortSwigger 推荐的测试流程大致是这样:
- 找端点:五条常用路径加上 GET/POST/x-www-form-urlencoded 三种方法轮着试。
- 跑内省:就是上面那段
__schema 查询,跑一次。
- 看 Web 流量:如果目标网站本身在用 GraphQL,打开浏览器开发者工具或 Burp 看真实查询,照着它的结构改。
- 捅参数:用 IDOR 思路改 ID、改邮箱、改外键,看返回是否越权。
3.3 IDOR 实战例子
PortSwigger 文档里有个标准的教学场景:电商站用 GraphQL 列出已上架商品,返回 ID 为 1/2/4(缺了 3)。攻击者直接查:
query { product(id: 3) { id name listed } }
返回结果里 product 3 其实存在,只是没上架而已——这就是典型的“列表过滤了,但单查没过滤”。这类 BOLA(Broken Object Level Authorization,破坏对象级授权)漏洞因为有内省辅助,枚举所有带 id 参数的类型非常快。
四、批处理与限速绕过
GraphQL 支持一个请求里发多个查询(batch / alias),攻击者正好拿这个来绕过限速。直接用别名让同一个 mutation 跑一百次:
mutation {
a: login(usr:"admin",pwd:"a") { token }
b: login(usr:"admin",pwd:"b") { token }
...
}
WAF 只数 HTTP 请求数,不看里面的别名,一百次登录尝试就只是一个 POST。Directus 在 CVE-2024-39895 里栽过这个跟头,CVSS(通用漏洞评分系统)6.5,普通认证用户就能把数据库负载线性放大。
五、防御五要点
- 生产环境关掉内省:Apollo、Hasura、graphql-java 都支持
introspection: false,线上必须关。
- 强制 POST + application/json:拒绝 GET 和 x-www-form-urlencoded,从源头掐掉 CSRF(跨站请求伪造)。
- 字段级授权:用 graphql-shield 等库做 resolver(解析函数)级鉴权,别只看“有没有登录”。
- 深度/复杂度限制:限制嵌套层数和字段数,挡住单查询 DoS;别名查询要单独计数。
- 错误信息脱敏:生产环境不回 stack trace 和原始 SQL,统一改成“内部错误”。
六、思考题
- 如果目标站用的是 GraphQL,你会先用哪一条 query 确认它在线?
- 内省被关掉之后,还有什么办法能推断 schema 结构?
- mutation 列表里出现了
deleteUser,但没有任何角色校验——这属于哪一类攻击面?
素材出处
- learn365 day20 原文(GraphQL Series Part-1,About/Introspection/Attacks/Practice/References 五节框架)
- graphql.org Learn 教程(Schemas/Typed system/Introspection 章节)
- hasura.io GraphQL Introspection 教程(
__schema / __type / __typename 三类查询示例)
- PortSwigger Web Security Academy GraphQL API Vulnerabilities(探测端点、IDOR、内省、批处理绕过)
- HackerOne 披露报告 #291531、#1132803
- HackerOne 博客 2024-07《How a GraphQL Bug Resulted in Authentication Bypass》
- Stingrai 2026 GraphQL API Vulnerabilities 综述
- dev.to 2026-09《GraphQL as Attack Surface: Introspection, Batching, and Schema Enumeration》