GraphQL 自 2015 年开源以来,凭借其"按需取数"的灵活特性迅速成为 API 设计的主流方案。然而,这种灵活性也带来了传统 REST API 中不存在的攻击面。许多团队在迁移到 GraphQL 时,往往只关注开发效率,而忽视了安全配置,导致内省查询泄露、批量攻击放大、注入风险等问题频繁出现。本文从攻击者视角出发,系统梳理 GraphQL 的三大类安全漏洞及其挖掘方法。
一、内省查询:攻击者的"地图绘制"工具
GraphQL 的内省(Introspection)机制允许客户端查询 Schema 本身的结构,包括所有类型、字段、参数、枚举值甚至废弃标记。对开发者而言这是便利的文档工具,对攻击者而言则是一张完整的攻击地图。
1.1 内省查询的危害
通过一个标准的 __schema 查询,攻击者可以获取:
- 所有可用的 Query、Mutation 和 Subscription 字段
- 每个字段的参数类型与是否必填
- 自定义标量类型和枚举值
- 已废弃但可能仍可调用的隐藏字段
例如,以下查询可以一次性拉取完整的类型系统:
query IntrospectionQuery {
__schema {
types {
name
kind
fields {
name
args { name type { name } }
}
}
}
}
攻击者拿到这些信息后,可以精准定位敏感操作,比如 deleteUser、updateRole、internalDebug 等本不应暴露的字段。
1.2 挖掘与检测方法
在生产环境中,内省查询应当被禁用。检测方法很简单:向目标 GraphQL 端点发送上述内省查询,如果返回完整 Schema,说明存在信息泄露。即使返回部分结果,也可能通过 __type 查询逐个探测类型。
绕过禁用内省的手段也值得关注。部分实现仅通过字段名黑名单过滤,攻击者可以使用别名(alias)或片段(fragment)绕过:
query { a: __schema { types { name } } }
1.3 防御建议
- 生产环境关闭内省,或在网关层根据来源 IP 白名单放行
- 使用 GraphQL 安全网关(如 Apollo Router 的 persisted queries)限制任意查询
- 对 Schema 进行定期审计,移除未使用的字段和类型
二、批量攻击:单请求放大为千次操作
GraphQL 允许在单个请求中嵌套多个查询和变更,这一特性被攻击者利用后,可以造成资源耗尽、暴力破解加速、数据批量泄露等后果。
2.1 查询批量与别名放大
攻击者可以在一个请求中通过别名重复调用同一字段:
query {
a1: login(username: "admin", password: "123")
a2: login(username: "admin", password: "456")
a3: login(username: "admin", password: "789")
# ... 重复上千次
}
如果后端没有对单请求的操作数量做限制,这就相当于把一次 HTTP 请求变成了上千次登录尝试,轻松绕过基于请求频率的限流。
2.2 嵌套查询导致资源耗尽
深度嵌套的查询会引发 N+1 问题或指数级的数据加载。例如:
query {
user(id: 1) {
friends {
friends {
friends {
friends { id name }
}
}
}
}
}
如果 resolver 没有做深度限制或复杂度分析,数据库可能被拖垮。攻击者还可以结合循环引用类型构造极深的查询链。
2.3 挖掘与检测方法
- 构造包含大量别名操作的请求,观察是否被限流或拒绝
- 使用工具如
graphql-cop、clairvoyance自动检测批量与深度限制 - 监控单请求的 resolver 调用次数,异常升高即为可疑
2.4 防御建议
- 实施查询复杂度分析(如
graphql-cost-analysis),为每个字段分配权重 - 限制单请求的别名数量和查询深度
- 对 Mutation 操作实施独立的速率限制,不依赖 HTTP 层限流
三、注入风险:GraphQL 不是注入的免疫区
许多开发者误以为 GraphQL 的类型系统天然防止注入,事实并非如此。GraphQL 只负责解析查询结构,参数值最终仍会传递给数据库、命令行或外部服务,注入风险依然存在。
3.1 SQL/NoSQL 注入
当 resolver 直接拼接参数到查询语句时,注入便会产生:
// 危险示例
const user = await db.query(
`SELECT * FROM users WHERE name = '${args.name}'`
);
攻击者可以传入 name: "admin' OR '1'='1" 绕过认证。NoSQL 场景下,传入对象类型的参数也可能触发操作符注入,例如 { $ne: null }。
3.2 命令注入与 SSRF
如果 resolver 调用系统命令或发起外部请求,参数未过滤时可能导致命令注入或 SSRF:
mutation {
importData(url: "http://internal-service:8080/admin")
}
此类字段常出现在"数据导入""Webhook 测试"等功能中,是挖掘的重点区域。
3.3 挖掘与检测方法
- 对内省获取的所有 String、ID 类型参数进行注入 payload 测试
- 关注接收 URL、文件路径、命令参数的字段
- 结合 Burp Suite 的 GraphQL 插件,对参数值进行模糊测试
- 注意 GraphQL 允许变量传递,测试时同时覆盖内联参数和变量两种方式
3.4 防御建议
- 使用参数化查询或 ORM,杜绝字符串拼接
- 对 URL 类参数实施白名单校验,禁止内网地址
- 对命令执行类操作进行严格输入验证,避免直接拼接
- 在网关层对参数值进行模式匹配,拦截典型注入特征
四、综合挖掘思路
在实际渗透测试中,建议按以下流程推进:
- 信息收集:尝试内省查询,获取完整 Schema;若被禁用,使用
clairvoyance等工具进行字段猜测 - 攻击面梳理:标记所有 Mutation、接收复杂参数的 Query、涉及外部调用的字段
- 批量与深度测试:构造别名放大和深层嵌套请求,观察响应时间与限流行为
- 注入测试:对字符串参数、URL 参数、ID 参数分别进行 SQL、NoSQL、命令注入测试
- 权限校验:检查是否存在越权访问,GraphQL 常在 resolver 层遗漏授权检查
五、总结
GraphQL 的安全问题本质上不是协议本身的缺陷,而是灵活性与安全配置之间的失衡。内省查询泄露了攻击所需的结构信息,批量特性放大了单请求的攻击能力,而注入风险则源于 resolver 实现的不严谨。对于开发团队,应在生产环境关闭内省、实施查询复杂度限制、坚持参数化查询;对于安全从业者,这三类漏洞应成为 GraphQL 渗透测试的标准检查项。只有在开发与安全两端同时发力,才能让 GraphQL 的灵活性真正服务于业务,而非攻击者。
未经允许不得转载:任鹏个人博客 » GraphQL 安全漏洞挖掘:内省查询、批量攻击与注入风险

