What is GraphQL?
GraphQL is an open-source data query and manipulation language for APIs and a runtime for fulfilling queries with existing data. Maintained and developed primarily via the GraphQL Foundation, GraphQL has had incredible adoption across various verticals and use cases with multiple tech organizations like Twitter, Facebook, Expedia, Shopify, and Hygraph.
Now that we have a definition, let’s understand some GraphQL terms.
The GraphQL specification defines a human-readable schema definition language (or SDL) that you use to define your schema and store it as a string.
Here's a short example schema that defines two object types: Book and Author:
At its simplest, GraphQL is about asking for specific fields on objects. Let's start by looking at a very simple query and the results we get when we run it.
Queries and Mutations:
In GraphQL, you can perform only two types of operations: queries and mutations. While we use queries to fetch data from the server (similar to read operation), we use mutations to modify server-side data(similar to patch operation). If queries are the GraphQL equivalent to GET calls in REST, then mutations represent the state-changing methods in REST (like DELETE, PUT, PATCH, etc).
Like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update.
Let's look at a simple example GraphQL query:
Let's look at a simple example GrapohQL mutation:
Getting Started With GraphQL Lab
Now that we have some basic definitions out of the way, let’s get our hands dirty and play around with an application built with GraphQL. If you would like to follow along and dive deeper into GraphQL pentesting, I suggest you use the Damn Vulnerable GraphQL Application (DVGA). Refer to the DVGA installation guide to set it up locally.
Here we have the DVGA running locally on port 5013. The easiest way to detect GraphQL is to look at your Burp History logs. There are a few common graphql endpoints like /graphql, /v1/graphql, etc.
If we generate some traffic in the DVGA, Burp will populate the target sitemap.
Note: You can also use the GraphQL Discovery wordlist for brute-forcing endpoints.
Now that we are confident that we are working with GraphQL technology on the server side, we should determine if the “introspection” feature is turned on.
GraphQL’s introspection feature is a convenient way for GraphQL to share details about itself with other developers or consumers of the GraphQL instance. When introspection is enabled, the entire GraphQL schema can be retrieved with a single query. This provides a significant advantage to penetration testers, bug bounty hunters, or security engineers.
Note: It is recommended to disable introspection in production to avoid data leakages. If you find this on a real pentest, it should be noted as an Information Disclosure finding.
The screenshot above shows that we made a POST request to an endpoint “/graphlq”. Let’s send that to the repeater so we can modify and play with this request.
The GraphQL syntax in Burp is a little hard to read. The good news is that a Burp Suite extension will help “beautify” the GraphQL queries. You can search for InQL in the Extension BApp Store and install it.
Once the InQL extension is installed and you’ve sent a request to the repeater, you will see a new tab, “InQL”
When we click the InQL tab, we have a much cleaner GrapQL Query to work with, and we can better understand what’s happening.
There’s a query sent to “getPastes” where “pastes” are public (true) and returning the id, title, content, ipAddr, userAgent, and the owner’s name from the database.
If introspect is turned on, we can edit this GraphQL query and retrieve the entire schema: queries, mutations, fields, etc., by sending a GraphQL introspection query.
To test if introspect is enabled, we paste the introspection query into the InQl tab and send the request.
As we can see from the response above, introspection is on. However, we have a small problem. Typically, a lot of data is returned, and it can be overwhelming to try and manually sort through this data. Not to fear though; we have yet another tool to help us analyze this data.
The GraphQL visualization tool, GraphQL-voyager. This tool can import a GraphQL schema into a map with nodes and edges. This can be an excellent way to get an overview of the schema's types, queries, and size.
To use this tool, we copy the response data and paste it into Graphql-voyager’s editor.
Now we have a visualization of the data that will help us better understand our target and identify vulnerabilities.
While we’re still in the enumeration phase, we should also check if the GraphQL interface is enabled. GraphQL has an Integrated Development Environment named GraphiQL (note the i) that allows constructing queries in a friendly user interface.
GraphiQL is usually found in paths such as: /graphiql or __graphiql. However, it can be in other places too. You can use this Nmap NSE script to enumerate GraphiQL endpoints.
Back in the DVGA, we browse to http://your-ip:port/graphiql. Here, we can see that it is enabled.
If we chose to do so, we could leverage this during our pentest to perform some testing without using Burp’s repeater. It can also be helpful to use this to craft GraphQL payloads as it has syntax highlighting and formatting features.
Testing for vulnerabilities
Now that we have enumerated the server let’s begin testing for vulnerabilities. There’s nothing special from a security standpoint about GraphQL; it can be vulnerable to all the typical web application vulnerabilities if not properly implemented, i.e., XSS, SSRF, SQLi, IDOR, etc. However, finding these issues takes some intuition and “eye training.” For example, whenever I see a user ID used in a query, I usually test for IDOR. Let’s look at the DVGA and find some dynamic user content that can be exploited.
In the side navigation bar, we can read users private and public “Pastes.”
Here we can create a “Paste” using Create Paste option in the sidebar.
Let’s create a paste and capture the request in BurpSuite.
When we click submit, we see that a POST request is sent to the “/graphql” endpoint. In the body, we have a mutation CreatePaste with the variables of the data we supplied via the browser.
Here we can see the “Paste” we just created in the UI.
The “Import Paste” feature is interesting as we could upload malicious files to the server or test for SSRF. However, when we capture the POST request in Burp, we see that a variable “path” is set as “/.” We can assume that this is the location on the server where the imported file will be stored.
If this feature isn’t properly implemented, it’s possible to inject OS commands here. Let’s test this by trying to read the password file since we know the hosting server is Unix based.
In the screenshot above, we can run OS commands by breaking out the first command by using the “;” character followed by a bash command. In this example, we are using “cat /etc/passwd” command to read the content of the passwd file.
We’re just scratching the surface.
While we kept this post at a very high level, I hope you’ve learned something new. GraphQL can be a big topic; there’s much more to learn. So, if you are interested in learning more about GraphQL, I’ll include some links that dive deeper into this topic.