公开的 GraphQL 内省:为何是个问题以及如何关闭
GraphQL 内省是一个内置查询,会返回 API 的完整 schema:每个类型、字段、参数和 mutation。它在开发中必不可少,但对于私有 API,在生产环境中不应可被访问。如果保持开启,它就是一张展示你整个后端攻击面的地图。
这是什么
每个 GraphQL 服务器都实现了一小组元字段,其中最主要的是 __schema 和 __type。只需向 /graphql 发送一次带有 `query { __schema { types { name fields { name } } } }` 的 POST 请求,就能返回完整的类型定义。
GraphiQL、Apollo Sandbox 和 Playground UI 等工具依赖内省来自动补全查询并渲染 schema 浏览器。在开发中这正是你想要的;但在生产环境的私有 API 上,这意味着世界上任何人都可以下载同一张地图。
为什么重要
schema 会列出你的模型、它们之间的关系以及你暴露的每个 mutation——包括从未打算被外部发现的管理、内部和已弃用字段。攻击者会利用它构建一份有针对性的操作列表,用于探测认证绕过、IDOR 和注入漏洞。
即使每个字段的授权都正确无误,refundOrder、impersonateUser 或 exportBackup 这类隐藏的 mutation 也不应自我暴露。内省把攻击者的侦察阶段从数小时的猜测变成了一次请求。
如何修复
当 NODE_ENV 为 production 时关闭内省。在本地保持开启,以便你的团队仍可在 Sandbox 中浏览 schema。
import { ApolloServer } from "@apollo/server";
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== "production",
});Yoga 提供了一流的插件;graphql-http 可以使用 graphql-js 提供的验证规则。两者都会在内省查询执行之前将其拦截。
// GraphQL Yoga
import { createYoga } from "graphql-yoga";
import { useDisableIntrospection } from "@graphql-yoga/plugin-disable-introspection";
export const yoga = createYoga({
schema,
plugins: process.env.NODE_ENV === "production" ? [useDisableIntrospection()] : [],
});
// graphql-http (or express-graphql)
import { NoSchemaIntrospectionCustomRule } from "graphql";
const validationRules = process.env.NODE_ENV === "production"
? [NoSchemaIntrospectionCustomRule]
: [];GraphiQL、Playground 和 Apollo Sandbox 是为了帮助人们浏览 schema 而存在的——它们不应出现在公开的生产端点上。请将它们置于认证检查之后,或者在生产构建中完全不挂载它们。
禁用内省是一项加固措施,而不是访问控制。每个字段仍然需要在 resolver 层面进行正确的授权——否则任何猜到字段名的人仍然可以调用它。把内省视为其中一层防护,而不是唯一的防护。
常见问题
隐藏 schema 不就是通过隐蔽实现安全吗?
部分是的——执着的攻击者仍然可以暴力猜测字段名。但让侦察变得廉价且完整,是值得阻止的。所有商业 GraphQL API 指南以及 OWASP GraphQL 速查表都建议在生产环境中禁用内省。
如果是希望开发者浏览 schema 的公开 API 呢?
有意识地发布 schema——发布 Schema Definition Language 文件、托管文档站点,或者只在认证后才开放内省。这与匿名 POST 请求就能返回所有内容截然不同。
AppSafe 如何检测这个问题?
AppSafe 会向 /graphql 和 /api/graphql 发送一个小型内省查询,并查找状态为 200 的 __schema 响应。如果有返回,就会在 API 安全类别中以中等严重程度标记。