最近、GraphQLを使ったAPIを構築するプロジェクトが急速に増えています。スマートフォンアプリのバックエンドやモダンなWebサービスで採用が広がり、GitHubやShopifyなど大手サービスも本番環境で活用しています。しかし「GraphQLのセキュリティリスクってREST APIと何が違うの?」「認証はかけているけど他に何をすべき?」という疑問を持つエンジニアや情シス担当者は多いはずです。
GraphQLには、その柔軟な設計思想から生まれる固有の脆弱性があります。イントロスペクション(スキーマ情報の外部公開)・バッチ処理攻撃・過剰なクエリによるDoSなど、REST APIでは見られなかったリスクが存在します。
この記事では、GraphQL APIが抱える主要な脆弱性の仕組みを攻撃者の視点で解説し、本番環境で今日から実装できる防御策を具体的に紹介します。GraphQLを開発・運用しているエンジニアはもちろん、社内システムの棚卸しをしている情シス担当者の方にも役立てていただける内容です。
GraphQL APIとは?REST APIとの違いと普及の背景
GraphQLはFacebookが2012年に社内開発し、2015年にオープンソースとして公開したAPIクエリ言語です。現在は多くのプロダクトで採用されており、モダンなシステム開発のスタンダードの一つになりつつあります。
REST APIとの最大の違いは、クライアント側が取得したいデータの形を自分で指定できる点です。
REST APIでは GET /users/1 というエンドポイントにアクセスすると、そのエンドポイントが返すように設計されたデータがすべて返ってきます。一方GraphQLでは、「名前とメールアドレスだけほしい」「最新5件の投稿とそのコメントも一緒に返してほしい」という要求を1回のリクエストで指定できます。
このデータ取得の柔軟性が開発効率を大幅に向上させる一方、セキュリティ上の新しいリスクを生み出しています。「便利さ」は「攻撃面の広さ」と裏表の関係にあるのです。
GraphQLが抱える主な脆弱性(攻撃の仕組みを知る)
GraphQL固有の設計思想から生まれる脆弱性を、攻撃者の視点で整理します。敵を正しく知ることが、実効性のある防御の出発点です。
1. イントロスペクション悪用(スキーマ情報漏洩)
GraphQLにはイントロスペクション(introspection)という機能があります。「このAPIでどんな操作ができるか」「どんなオブジェクトやフィールドが存在するか」をクエリで問い合わせられる機能で、開発時のドキュメント自動生成などに使われます。
問題は、この機能を本番環境で有効にしたまま外部に公開してしまうケースが多いことです。
攻撃者は次のようなシンプルなクエリを送るだけで、APIの全スキーマ構造を取得できます。
# イントロスペクションクエリの例(攻撃者はこれでスキーマを丸ごと取得できる) { __schema { types { name fields { name type { name } } } } }
REST APIに例えるなら「API仕様書を全文そのまま外部公開している状態」です。どのオブジェクトが存在し、どのフィールドに何のデータが格納されているかが丸見えになり、攻撃者はその情報を基に次の攻撃を組み立てます。
2. GraphQLインジェクション攻撃
GraphQLのクエリ変数やミューテーション(データ更新操作)に、不正な入力を混入させる攻撃です。
バックエンドのデータベースの種類や実装によって攻撃の形は異なります。
・NoSQLインジェクション: MongoDBなどのNoSQLデータベースが接続先の場合、演算子を悪用した不正クエリが成立するケースがあります
・SQLインジェクション: リゾルバー(データ取得の実処理部分)が動的にSQLを組み立てる実装では、GraphQL変数経由のSQLインジェクションが発生します
・コードインジェクション: テンプレートエンジンや外部コマンドがリゾルバーから呼ばれる設計では、他の形のインジェクションも成立します
GraphQL自体はデータベースへの接続方法を規定しないため、リゾルバーの実装の質が直接セキュリティレベルに影響します。「GraphQLを使っているから安全」という考え方は誤りです。
3. バッチ処理悪用(Batching Attack)
GraphQLは複数のクエリやミューテーションを1つのHTTPリクエストにまとめて送る「バッチ処理」をサポートしています。これを悪用したのがバッチ処理攻撃(Batching Attack)です。
典型的な攻撃パターンはブルートフォースのレート制限回避です。通常のレート制限は「1リクエストあたり1回のログイン試行」を前提に設計されています。しかしGraphQLのバッチ機能を使うと、1リクエストの中に数百・数千のログイン試行ミューテーションを詰め込んで送ることができます。
実質的にレート制限をすり抜けられるため、パスワード辞書攻撃の効率が桁違いに上がります。OWASPもGraphQL固有の脅威の一つとして「Batching Attack」を明示的に分類しています。
4. 過剰なクエリ(Deep Query・Complex Query攻撃)
GraphQLの「ネストされたクエリ」機能がDoS攻撃のベクトルになります。
「ユーザー → そのユーザーの投稿 → 投稿にコメントしたユーザー → そのユーザーの投稿 → …」という形で無限に深くネストされたクエリを送りつけられると、サーバーは処理のたびにデータベースへの問い合わせを繰り返します。深度制限がなければ、たった1リクエストでサーバーリソースを使い果たすことも可能です。
同様の問題として、ネストは浅いが横方向に大量のフィールドを並べる「幅攻撃」もあります。大量のデータを1リクエストで取得させ、ネットワーク帯域とサーバーの処理能力を同時に圧迫します。
5. フィールド提案による情報漏洩
多くのGraphQLライブラリはタイポ(入力ミス)を検知すると「もしかして〇〇ですか?」とサジェストを返す機能を持っています。
例えば passwrd というフィールドを問い合わせると、「password というフィールドが存在します」とレスポンスに含まれてしまうことがあります。攻撃者はこれを利用し、意図的にタイポを使った問い合わせを送ることで、存在するフィールド名を一つひとつ「掘り起こす」ことができます。
イントロスペクションを無効化していても、フィールド提案機能が有効なままでは類似の情報漏洩が発生します。
具体的な防御手順
攻撃の仕組みを理解した上で、実際に実装できる防御策を順番に見ていきます。
1. 本番環境でイントロスペクションを無効化する
最優先で実施すべき対策です。本番環境のGraphQL APIでイントロスペクションを無効化してください。
Apollo Server(Node.js向けの代表的なGraphQLサーバーライブラリ)の例:
# Apollo Server v4 の設定例(JavaScript) const server = new ApolloServer({ typeDefs, resolvers, // NODE_ENV が production のときはイントロスペクションを無効化 introspection: process.env.NODE_ENV !== 'production', });
また、フィールド提案機能も本番環境では無効化します。Apollo Serverではカスタムプラグインでサジェストレスポンスをフィルタリングする方法、他のライブラリでも同様の設定オプションが提供されています。
開発用ドキュメントのためにイントロスペクションが必要な場合は、社内IPからのアクセスのみに制限するか、管理者権限の認証済みユーザーにのみ許可する設計にしましょう。
2. クエリ深度・複雑度制限を設定する
Deep Query攻撃を防ぐため、クエリの最大深度(ネスト階層数)と複雑度(フィールド数の重み付き合計)に制限を設けます。
# graphql-depth-limit ライブラリを使った深度制限の例(Node.js) const depthLimit = require('graphql-depth-limit'); const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ depthLimit(5), // ネストは最大5階層まで許可 ], });
深度の上限値はアプリケーションの正当な要件に合わせて設定しますが、一般的に10を超える深度が必要なケースは設計自体の見直しも検討します。複雑度制限には graphql-query-complexity などのライブラリが利用できます。
3. レート制限とバッチ処理を制御する
バッチ処理攻撃対策として、クエリのコストを計算してレート制限をかける方法が有効です。
・IPベースのレート制限: APIゲートウェイ層またはWebサーバー層でIPアドレス単位のリクエスト数制限を設ける
・認証トークンベースの制限: ログイン済みユーザーのトークン単位で一定時間内のリクエスト数を制限する
・バッチリクエストの上限設定: 1リクエストに含められるオペレーション数に上限を設ける(例:最大10件まで)
・クエリコスト計算: フィールドの数やネストの深さに応じてコスト点数を算出し、一定コストを超えるクエリを拒否する
nginxやApacheのレート制限モジュール、あるいはAWS API Gatewayなどのマネージドサービスを活用すると、アプリケーションコードを変更せずにレート制限を追加できます。
4. 入力バリデーションと認可チェックを徹底する
すべてのGraphQL変数・引数にバリデーションを実装します。GraphQLのスキーマ定義による型チェックはあくまで第一層であり、ビジネスロジック層での追加検証が必要です。
特に重要なのが認可チェックです。GraphQLのスキーマレベルでは「誰が何を見られるか」を表現できないため、各リゾルバーの中でオブジェクトレベルの権限確認を実装しなければなりません。
「認証は通っているから全フィールドにアクセスできる」という思い込みが、他ユーザーのデータを取得できるIDOR(安全でない直接オブジェクト参照)につながります。ユーザーAがユーザーBのデータを取得できないよう、リゾルバーごとに「リクエスト元のユーザーIDとデータオーナーのIDが一致しているか」を確認する実装が必要です。
Linuxのファイル権限管理や認証・認可の基礎については、姉妹サイトLinuxMaster.JPでも詳しく解説しています。
中小企業でも今日からできること
「GraphQLを使っているかどうかも把握していない」という企業も少なくありません。まず現状把握から始めましょう。
・利用状況の把握: 社内・社外向けシステムでGraphQL APIが稼働していないか開発担当者に確認する
・イントロスペクション確認: /graphql エンドポイントが外部からアクセス可能な場合、{"query": "{__typename}"} を送って正常レスポンスが返るか確認する(返る場合はイントロスペクション有効の可能性がある)
・外部公開エンドポイントの洗い出し: WAFやAPIゲートウェイのアクセスログで /graphql へのリクエストを検索する
・ライブラリのバージョン管理: 使用中のGraphQLライブラリを最新のセキュリティパッチが当たったバージョンに保つ
・外部ベンダーへの確認: システム開発を外部委託している場合、上記の対策が実装されているかベンダーに確認を依頼する
すべてを一度に実施するのは難しくても、「イントロスペクションが本番環境で有効になっていないか確認する」だけなら今日中にできます。まずそこから着手してください。
よくある誤解と注意点
【誤解1】「内部向けシステムだから大丈夫」
社内システムであっても、フィッシングやマルウェアでPCが侵害された場合には内部から攻撃が起きます。VPNや社内ネットワーク越しのアクセスだけを前提にした設計はゼロトラストの考え方と相反します。内部システムでも認可チェックとレート制限は必要です。
【誤解2】「認証さえかけておけば安全」
認証(そのユーザーが本人かどうか)と認可(そのユーザーが何をできるか)は別の問題です。認証済みの一般ユーザーが他ユーザーの個人情報を取得できる横断的アクセス(IDOR)は、認証の有無とは無関係に発生します。リゾルバーごとの認可チェックを必ず実装してください。
【誤解3】「GraphQLはREST APIより危険」
GraphQLにはREST APIにはない固有のリスクがある一方、適切に実装すればデータの過剰取得・過小取得が減り、攻撃面を絞れる側面もあります。「GraphQLだから危険」ではなく「設定や実装の問題」です。この記事で紹介した対策を実施すれば、十分に安全な運用が可能です。
【注意】汎用Webスキャナーへの過信
OWASP ZAPやNessusなど汎用のWebアプリケーションスキャナーは、GraphQL固有の脆弱性(バッチ処理攻撃・イントロスペクション悪用など)を検知しないケースがあります。GraphQL専用のテストツールを組み合わせた確認を定期的に実施することを推奨します。ただし、テストは必ず本番環境ではなくステージング環境で、適切な権限のもとで行ってください。
本記事のまとめ
| 脆弱性 | 攻撃者のゴール | 主な対策 | 難易度 |
|---|---|---|---|
| イントロスペクション悪用 | スキーマ情報収集・攻撃設計 | 本番環境で無効化 | 低(設定変更のみ) |
| GraphQLインジェクション | 不正データ取得・変更・削除 | 入力バリデーション・型安全な実装 | 中 |
| バッチ処理攻撃 | レート制限回避・認証突破 | バッチ上限設定・クエリコスト計算 | 中 |
| 過剰なクエリ(DoS) | サービス停止・負荷増大 | 深度制限・複雑度制限 | 低(ライブラリで対応) |
| フィールド提案漏洩 | スキーマ構造の推測 | サジェスト機能の無効化 | 低(設定変更のみ) |
GraphQLは現代のAPI開発に欠かせない技術ですが、その柔軟性の裏に隠れたリスクを正しく理解することが安全な運用の第一歩です。
対策の優先順位は「①イントロスペクション無効化 → ②フィールド提案の無効化 → ③クエリ深度・複雑度制限 → ④レート制限・バッチ制御強化 → ⑤リゾルバーごとの認可チェック徹底」です。
1つひとつの対策は決して難しいものではありません。まず今日できることから着手し、段階的にGraphQL固有のセキュリティを強化していきましょう。
