GraphQL 安全
GraphQL 提供了强大的灵活性,但也引入了独特的攻击向量:查询复杂度攻击、 内省滥用、授权绕过和信息泄露。
GraphQL 中的常见漏洞
1. 查询深度 / 复杂度攻击
深度嵌套的查询会消耗过多资源,从而导致 DoS:
query MaliciousQuery {
user(id: "1") {
posts {
comments {
author {
posts {
comments {
author {
posts {
# ... 无限嵌套
}
}
}
}
}
}
}
}
}
2. 内省滥用
在生产环境中启用内省会暴露完整的 schema,便于攻击者进行侦察:
query IntrospectionQuery {
__schema {
types {
name
fields {
name
type {
name
}
}
}
}
}
3. 授权失效
授权必须在解析器中进行,而不仅仅是在顶层查询中。当嵌套字段未检查授权时, IDOR 很常见。
4. 注入攻击
如果输入未在解析器中进行清理,GraphQL 同样无法幸免于 SQLi、NoSQLi。
必备防护措施
查询深度限制
// Apollo Server 示例
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)] // 最大深度 5
});
查询复杂度分析
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
validationRules: [
createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 5,
listFactor: 10
})
]
});
速率限制
- 基于查询:限制每个用户每分钟的查询数
- 基于复杂度:复杂度点数预算
- 成本分析:为每个字段分配成本
GraphQL 中的授权
字段级授权
const resolvers = {
Query: {
user: async (parent, { id }, context) => {
// 查询级授权
if (!context.user) {
throw new AuthenticationError('Not authenticated');
}
return getUserById(id);
}
},
User: {
email: (user, args, context) => {
// 字段级授权
if (context.user.id !== user.id && !context.user.isAdmin) {
return null; // 对其他用户隐藏邮箱
}
return user.email;
},
ssn: (user, args, context) => {
// 极其敏感的字段
if (!context.user.isAdmin) {
throw new ForbiddenError('Admin only');
}
return user.ssn;
}
}
};
基于指令的授权
type User @auth(requires: AUTHENTICATED) {
id: ID!
username: String!
email: String! @auth(requires: OWNER_OR_ADMIN)
ssn: String! @auth(requires: ADMIN)
}
内省防护
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production',
// 或按角色控制
plugins: [{
requestDidStart() {
return {
didResolveOperation({ request, context }) {
if (request.operationName === 'IntrospectionQuery'
&& !context.user?.isAdmin) {
throw new ForbiddenError('Introspection disabled');
}
}
}
}
}]
});
输入校验
- Schema 校验:GraphQL 会自动校验类型
- 自定义标量:带校验的 Email、URL、DateTime
- 输入清理:在解析器中使用输入前先进行清理
- 参数化查询:在数据库中使用 prepared statements
批处理与 DataLoader
防止可被利用于 DoS 的 N+1 问题:
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds) => {
const users = await getUsersByIds(userIds);
return userIds.map(id => users.find(u => u.id === id));
});
const resolvers = {
Post: {
author: (post, args, { userLoader }) => {
return userLoader.load(post.authorId); // 已批处理!
}
}
};
监控与日志
- 查询日志:记录所有查询及其元数据(用户、IP、时间)
- 错误跟踪:使用 Sentry、Datadog 跟踪异常
- 性能监控:Apollo Studio、GraphQL Hive
- 异常检测:对可疑查询(过于复杂等)发出告警
安全工具
- GraphQL Armor:安全中间件套件
- graphql-shield:带规则的权限层
- InQL:用于 GraphQL 渗透测试的 Burp Suite 扩展
- BatchQL:用于 GraphQL 的安全测试工具
- graphql-cop:安全审计工具
OWASP GraphQL Top 10
- Broken Object Level Authorization
- Broken Authentication
- Excessive Data Exposure
- Resource Exhaustion
- Broken Function Level Authorization
- Mass Assignment
- Security Misconfiguration
- Injection
- Improper Assets Management
- Insufficient Logging & Monitoring
安全开发实践
- 在所有解析器中实现授权
- 在生产环境中禁用内省
- 限制查询深度和复杂度
- 积极的速率限制
- 使用 DataLoader 防止 N+1
- 在处理前清理输入
- 实施全面的日志记录
- 定期进行安全审计和渗透测试
最终建议
与 REST 相比,GraphQL 要求转变安全思维。客户端的灵活性 需要服务器端提供强健的防御。从一开始就实施深度限制、复杂度分析和 字段级授权。使用监控工具检测 滥用模式。定期使用 InQL、BatchQL 等专用工具进行测试。
