GraphQLセキュリティ
GraphQLは強力な柔軟性を提供しますが、独自の攻撃ベクトルをもたらします: クエリ複雑度攻撃、 イントロスペクションの悪用、認可バイパス、情報漏洩などです。
GraphQLでよくある脆弱性
1. クエリの深さ / 複雑度攻撃
深くネストされたクエリは、過剰なリソースを消費してDoSを引き起こす可能性があります:
query MaliciousQuery {
user(id: "1") {
posts {
comments {
author {
posts {
comments {
author {
posts {
# ... 無限にネスト
}
}
}
}
}
}
}
}
}
2. イントロスペクションの悪用
本番環境でイントロスペクションが有効になっていると、スキーマ全体が公開され、偵察が容易になります:
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
})
]
});
レート制限
- クエリベース: ユーザーごとに1分あたりのクエリ数を制限する
- 複雑度ベース: 複雑度ポイントの予算
- コスト分析: 各フィールドにコストを割り当てる
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');
}
}
}
}
}]
});
入力検証
- スキーマ検証: 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
セキュアな開発プラクティス
- すべてのリゾルバに認可を実装する
- 本番環境でイントロスペクションを無効化する
- クエリの深さと複雑度を制限する
- 積極的なレート制限
- N+1を防ぐためにDataLoaderを使用する
- 処理前に入力をサニタイズする
- 包括的なロギングを実装する
- 定期的なセキュリティ監査とペネトレーションテスト
最終的な推奨事項
GraphQLはRESTと比べてセキュリティに対する考え方の転換を求めます。クライアントの柔軟性は サーバー側の堅牢な防御を必要とします。深さ制限、複雑度分析、 フィールドレベルの認可を最初から実装してください。モニタリングツールを使って 悪用のパターンを検知しましょう。InQLやBatchQLといった専用ツールで定期的にテストしてください。
