Fantastic GraphQL Bugs And Where To Find Them
Fantastic GraphQL Bugs and Where to Find Them
Intro
Back in 2022, I presented a talk at OWASP NZ Day about common GraphQL bugs that my colleagues and I had observed during our penetration testing work. This post will summarize a lot of that, as well as recommending some ways to mitigate these issues and secure GraphQL instances. Lets dig in!
What is GraphQL
Simply put, GraphQL is a query language for APIs, similar to REST and SOAP. Originally created for internal use at Facebook, and was later open-sourced in 2015. It is schema based, and allows users to retrieve data in a more efficient manner.
GraphQL vs REST
GraphQL was designed for efficiency, you retrieve only what you want, nothing more nothing less. This is beautifully shown in the image below:
![]() |
---|
GraphQL Burger Analogy |
With REST, you retrieve the entire pre-defined contents of /burger
, regardless of whether you need it or not. Whereas with GraphQL, you can pick the individual components of burger
as defined in the schema. Don’t like lettuce? Easy, no lettuce for you. Oh you want Bun Patty Patty Cheese Cheese Bun? Sure here you go!
If Hogwarts were to implement a system for keeping track of students and house points, the following diagram shows the differences between using REST and GraphQL for this.
![]() |
---|
GraphQL vs REST |
Instead of maintaining three different API endpoints, this can all be handled by one GraphQL system.
Queries and Mutations
Each system/software/etc has its own terminology, and GraphQL is no different. Here we have things called Queries and Mutations.
![]() |
---|
mutation dot gif |
Nothing to be alarmed about, Queries are basically read
operations or GET
![]() |
---|
GraphQL Query |
Mutations and change operations such as create, update and delete
or POST, PUT DELETE
![]() |
---|
GraphQL Mutations |
Accio Vulnerabilities
GraphQL isn’t immune to many of the security vulnerabilities we see within APIs traditionally. We still have things such as:
- Insecure Direct Object Reference (IDOR)
- SQL Injection (SQLi)
- Broken Access Control
- Denial of Service (DoS)
- And more!
Tools?
There is lots of helpful tooling out there that we can use to test GraphQL.
- Burp Suite Plugins - inQL and GraphQL Raider - These make your life so much easier when testing an application through the Burp Suite Proxy.
- GraphQL Voyager - Maps out the GraphQL schema and gives us a nice visual representation.
- Insomnia.rest - API Client
- GraphQL itself 😏
GraphQL Introspection
Introspection is a feature of GraphQL which allows you to query and retrieve the API schema to see what API functions are available for you. Good Guy GraphQL.
This is similar to how Swagger docs are sometimes available for REST APIs, and in both of these cases, it is generally a good idea to have these not available to the public.
In GraphQL, you use an introspection query to retrieve the scheme information. An example of a full query looks like this:
{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}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 ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}}
Thanks Clippy GraphQL
Look for the following endpoints on GraphQL servers for inbuilt querying functionality:
- /v1/explorer
- /v1/graphiql
- /graph
- /graphql
- /graphql/console/
- /graphql.php
- /graphiql
- /graphiql.php
It’s GRAPH-queueee-ell not graphical
GraphQL Voyager is an excellent tool which can be used to map out the schema. Very helpful for complex schemas as it gives us a visual representation. Available as a service online as well as open-source an package.
Smaller schemas are easy to follow and read.
![]() |
---|
A Simple Schema |
But it really shines with complex schemas as seen below:
![]() |
---|
A Complex Schema |
Denial of Service (DoS)
As far as vulnerabilities go, DoS and DDoS get a lot of attention because of the widespread impact it can cause. Within a GraphQL environment, queries can get quite complex because they can be nested and called recursively.
Take an example of a blog:
- You would typically have Users and Posts.
- A post has attributes such as title, author etc.
- Each user can have multiple posts
- Each post will have (at least) a user.
- We can call this recursively.
e.g.
![]() |
---|
Example of a recursive query |
By crafting increasingly complex queries, we can place additional load on the backend system, as seen in the images below:
![]() |
![]() |
---|---|
A Standard Query | A Recursive Query |
DoS Mitigation
We can employ some simple techniques to mitigate the risk and impact of this issue:
- Limit Maximum Query Depth
- By default, GraphQL sets this to max. This should be limited to something that makes sense for your application.
- Throttle Requests Based on Server Time or Query Complexity
- Audit Queries Before Production
Some useful links for this:
- https://www.npmjs.com/package/graphql-validation-complexity
- https://github.com/4Catalyzer/graphql-validation-complexity
- https://github.com/slicknode/graphql-query-complexity
Insecure Direct Object Reference (IDOR)
This video shows a pretty standard blog site, with a login page and posts. When we click on settings, we can only see our own info, which the API retrieves based on user ID. In this case, authorisation was not being checked, allowing us to see other user’s settings. Depending on how the application is configured, this may lead to cross-tenant issues too!
IDOR Mitigation
- Use GUIDs instead of IDs.
- This is a defense-in-depth measure and increases the complexity of user IDs.
- Implement Role Based Access Controls (RBACs)
- Always validate that the user is authorised to perform the action that they are requesting.
Injection
The video shows how SQL injection can be possible as GraphQL by default does not do any input sanitisation. The application has an admin panel which allows the user to ping a server and returns the status code.
This application is taking our input and loading it directly into a SQL query. We can use the classic SQL test payload ` ‘SLEEP(5)` . The server responds by responding after 5 seconds,
Injection Mitigation
Same way we would mitigate traditional injection style attacks.
- Input Validation - All user input should be treated as untrusted and sanitised before being used by the application.
- Ideally this should follow an allow-list approach as opposed to deny-list. Only allow your specified characters to be used.
- In this case, for a server ping, make sure the input is numeric and/or matches IP Address formatting.
- Use Parameterised queries.
Real Life Examples
On a client engagement, we were once testing a crowdfunding style application using GraphQL. Front-end was configured securely and access-control was enforced well, however not on GraphQL…
We sent an introspection query to the GraphQL endpoint and got a full schema of the application functionality, which revealed a lot unused functionality, including admin functionality.
Admins had the ability to allocate funds and approve funding requests etc, so we were basically able to request funding for a new project and approve it ourselves.
Oh, and this could all be done unauthenticated. 💀
Key Takeways
- Treat All User Input as Untrusted.
- Conduct Input Validation.
- Limit Query Depth.
- Enforce Robust Access Controls and Authorisation Checks.
- Don’t Forget To Apply These Security Controls on GraphQL!