Lessons Learned While Pentesting GraphQL
Sean Verity //
GraphQL is one of those technologies that I heard about several years ago but had not encountered during an actual pentest. After reading a blog or two, I remember thinking “JSON, database queries, got it” and then I moved on. I didn’t see GraphQL during an actual pentest until about a month ago.
There were a few pain points that I worked through to bootstrap my GraphQL pentesting chops. Hopefully, I can help you avoid these same pain points when you come across GraphQL for the first time during a pentest, bug bounty hunt, or CTF.
There are several tools out there. I started out doing things manually, which is good to learn GraphQL, but gets really hard, really fast when you start getting into something beyond a very basic GraphQL query. Therefore, I’m going to suggest using a tool that can automate, or at the very least, create queries that are good from a syntax perspective.
This blog also assumes that GraphQL introspection is enabled. There are tools to enumerate GraphQL schemas when introspection is disabled but that is outside the scope of this blog. But, in case you need one, here you go:
How to Find GraphQL
The easiest way to find GraphQL is to look at your Burp History logs. In the HTTP History tab, filter by the search term, “graphql,” and see if anything pops up. You could also search your target in the “Site map” tab for “graphql”. If the app is using GraphQL on the frontend, you’ll see an endpoint that includes “graphql” somewhere in a URL.
But what if GraphQL wasn’t intended to be exposed?
Well, there’s a GraphQL SecList for that! Feed the GraphQL SecList to Gobuster or your favorite forced browsing tool for effect. (BTW – Did you see our recent blog on Gobuster? https://www.blackhillsinfosec.com/for-web-content-discovery-who-you-gonna-call-gobuster/)
After finding GraphQL, one of the first things you might want to do is send the request to Burp Suite’s Repeater tab for further analysis. One thing that jumped out for me is that GraphQL is VERY transparent. Take a look at the following error message:
There’s actually a few things wrong with this query, but from reading the error message, it is plainly obvious where we can start to fix it.
GraphQL also has this wonderful feature called “introspection.” 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 pentesters, bug bounty hunters, or anyone looking for vulnerabilities.
The GraphQL visualization tool, graphql-voyager, can import a GraphQL schema and turn it into a map with nodes and edges. This can be a nice way to get an overview of the types, queries, and size of the schema. Here’s a snippet from an example GraphQL schema that describes a popular movie franchise set in a galaxy far, far away:
As a side note, the first time I saw GraphQL during a pentest, I imported a schema into graphql-voyager and started writing GraphQL queries in Repeater from scratch. This was fun from a learning perspective and was no big deal for simple queries. I quickly realized that this approach was unsustainable though as I dug further into the schema where there were more complex queries. It was really easy to include a misplaced curly brace.
And then I learned about this awesome project called InQL (Introspection Query Language). InQL can retrieve a GraphQL schema from a remote server through GraphQL’s introspection feature and parse it into request templates. InQL can also import a file if you have the schema saved on disk. InQL can be run as a CLI tool or as a Burp Suite extension. Running InQL as a Burp Extension offers the benefit of a workspace where you can copy and paste the request templates into other tools such as Repeater or Intruder for further testing.
Installing the InQL Burp Suite Extension
Installing the InQL Burp Suite extension is as simple as searching for it in the BApp store and clicking the install button.
After installing InQL, fetching a schema is as simple as copying the GraphQL endpoint into the address bar and clicking the “Load” button. It might take a few seconds for the schema to load. Upon completion, the interface should look similar to below.
The schema in the screenshot above was grabbed from Damn Vulnerable GraphQL Application. As can be seen in the figure above, the InQL will parse the schema into queries, mutation, and subscriptions. InQL will also do you the favor of documenting the associated fields and arguments in request templates.
When you come across GraphQL during a pentest, you might find subscriptions, but you will almost certainly come across queries and mutations. Queries, as you likely guessed, will fetch data from the GraphQL data store. I like to think of mutations as functions because their purpose is to modify data in the GraphQL data store. Both queries and mutations can accept arguments which are good to fuzz.
After you load a schema into InQL, you can inspect the objects and copy the request templates from InQL to Repeater for further testing.
When you copy request templates from InQL to Repeater, pay attention to which tab you copied from. Copying a request template from the GraphQL tab into Repeater will not work.
However, you can copy a request from the “Raw” tab into the body of a POST request in Repeater.
Ok, so actually, when InQL is installed, an extra tab is included in Repeater to paste or tweak your GraphQL queries. The important thing to remember though is that you need to put the right query format into the right Repeater tab. In the example below, a GraphQL query from InQL was copied into the GraphQL tab in Repeater.
When querying the systemHealth type, we see a reasonable response. As a basis for comparison, here’s what happens when you accidentally put a “raw” query into the GraphQL query tab.
Now look at that beautiful error message. Isn’t that nice? Even though the query was not formatted correctly, GraphQL is generous enough to respond with a helpful error message that you can use to troubleshoot.
From this point, you could continue manually testing with Repeater, or send it to Intruder for fuzzing. Think of the GraphQL API as a roundabout way of interacting with the application’s back end. Here are a few hacking ideas:
- Striking out on fuzzing “normal” requests? Try fuzzing the GraphQL queries / mutations and their arguments.
- Is the app doing a good job at preventing brute forcing? Find a query or mutation that accepts a password as an argument and see if there’s brute force prevention there. More on this topic here: https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/04-Authentication_Testing/10-Testing_for_Weaker_Authentication_in_Alternative_Channel
- Look at all the queries and mutations that look interesting and try them out. You might get lucky and come across a query that reveals secrets about the application.
Other GraphQL Testing Tips
- Found a query that needs arguments?
- Search Burp History for the arguments. Search for substrings or variants of what’s in the schema. As an example, the schema might have enumerated an argument named “fooBar”. Try searching for “foo” in Burp History in case the front end uses a different naming convention such as “foo_bar”. See if Burp’s Target Analyzer uncovered GraphQL arguments for you as well. In the Sitemap tab, right-click on the target’s base node, then choose “Engagement Tools > Analyze Target”.
- Watch for error messages in the GraphQL response. GraphQL will clearly tell you what is wrong with your query. You may have forgotten an argument or passed an integer when it expected a string, etc.
- Take Modern WebApp Pentesting w/ BB King. Other testing techniques that will come in handy for testing GraphQL (such as NoSQL injection) are covered.
One of the most frustrating things that I encountered when testing GraphQL was when a query would work and then the next day, it didn’t.
The problem was usually pretty simple and easy to overlook. Here are a few things to check if you’re in that situation:
- Make sure you’re sending a POST request. A GET request might trigger an error message to tip you off that GraphQL is present. But, you gotta use POST requests for valid queries.
- Make sure that the following request header is present: Content-Type: application/json
- Read error messages in the response and modify your request accordingly.
- Double-check which context you pasted a query into. GraphQL queries can only be pasted into the GraphQL context in Repeater. Raw requests will not work when pasted into the GraphQL context in Repeater.
- If you don’t see the “InQL” dropdown option in Repeater: Copy a query from InQL into “raw” format into a POST request in Repeater, then click Send. This will usually bootstrap the InQL context in Repeater.
GraphQL Security Testing Resources for Further Learning
Ready to learn more?
Level up your skills with affordable classes from Antisyphon!
Available live/virtual and on-demand