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

  1. Broken Object Level Authorization
  2. Broken Authentication
  3. Excessive Data Exposure
  4. Resource Exhaustion
  5. Broken Function Level Authorization
  6. Mass Assignment
  7. Security Misconfiguration
  8. Injection
  9. Improper Assets Management
  10. Insufficient Logging & Monitoring

セキュアな開発プラクティス

  • すべてのリゾルバに認可を実装する
  • 本番環境でイントロスペクションを無効化する
  • クエリの深さと複雑度を制限する
  • 積極的なレート制限
  • N+1を防ぐためにDataLoaderを使用する
  • 処理前に入力をサニタイズする
  • 包括的なロギングを実装する
  • 定期的なセキュリティ監査とペネトレーションテスト

最終的な推奨事項

GraphQLはRESTと比べてセキュリティに対する考え方の転換を求めます。クライアントの柔軟性は サーバー側の堅牢な防御を必要とします。深さ制限、複雑度分析、 フィールドレベルの認可を最初から実装してください。モニタリングツールを使って 悪用のパターンを検知しましょう。InQLやBatchQLといった専用ツールで定期的にテストしてください。