Hello and welcome to part two of my Hacking GraphQL series. We have already gone through basics in my previous post. I highly recommend you to go through part one if you have not already. In this blog we will go through a demo application called Generic University created by awesome InsiderPHD to understand some basic bugs and craft a methodology to test GraphQL.
Vulnerabilities in GraphQL implementation
Let’s cut to the chase and come straight to the point, all the bugs which can be found in REST APIs can be found in the GraphQL. Which includes the following but not limited to -
- API1:2019 — Broken object level authorization
- API2:2019 — Broken authentication
- API3:2019 — Excessive data exposure
- API4:2019 — Lack of resources and rate limiting
- API5:2019 — Broken function level authorization
- API6:2019 — Mass assignment
- API7:2019 — Security misconfiguration
- API8:2019 — Injection
- API9:2019 — Improper assets management
- API10:2019 — Insufficient logging and monitoring
Implementation of GraphQL is a complex process and due to this fact few bugs are common and prevalent in GraphQL. Some of them are -
- Authentication issues
- Authorization issues
- Information disclosure
- Denial of Service
GraphQL — Specific Bug
Batching attack: To understand this attack, basic knowledge about batching is required. According to the documentation,
“Batching is where multiple requests for data from a back end are collected over a short period of time and then dispatched in a single request to an underlying database or microservice by using a tool like Facebook’s DataLoader.”
You would have an idea where the things can go wrong. We can send multiple requests to single endpoint in a single request without worrying about getting blocked.
There are multiple tools available but we will be looking at some of them in this post.
GraphQL Voyager gives a visual representation of the schema for a specific graphql implementation based on introspection query results. We have already seen it in the previous blog post.
InQL can be used as a standalone script or as Burp-Suite extension. For burp lovers and power users this is one of the best tools to test for GraphQL issues. InQL will only take the graphql endpoint as input and automatically sends the introspection query, displaying the data. One of the pros is that we can send the request to the repeater for further inspection and tempering. On top of that, it generates the documentation and also has a -built-in GraphQL IDE!!
InQL in action -
This is a tool that lists the different ways of reaching a given type in a GraphQL schema. It takes the introspection query result as input. You can find more about it in this blog post by the creator of this tool.
GraphQLmap is a scripting engine to interact with a graphql endpoint for pentesting purposes. It has multiple features like dumping schema, autocomplete queries, field fuzzing and some common injection fuzzing inside graphql fields.
Let’s proceed on approach the target which uses GraphQL.
There can be multiple ways we can identify if the application uses GraphQL.. Some of which includes -
- Observing the requests and response structure. query, mutation or other words which indicates the GraphQL being used
- Presence of GraphiQL endpoint. If you don’t find a usual place, fuzzing with common endpoint names is an option.
- Errors disclosing the GraphQL related details.
2. Use Introspection query
By using introspection query, we can identify the available resources in the current API schema. Some of the samples can be found in the previous post. InQL also sends introspection query in the background. If you want to run the queries manually, refer -
3. Visualizing the introspection results
After receiving the response, we need to visualize the data. Fire up GraphQL Voyager and paste the results. It will give you an idea on how the GraphQL structure is defined.
4. Listing Mutations
If you have observed the output of GraphQL Voyager, you would have realized that it does not show mutations. We can use graphiql endpoint or even InQL data to check all the mutations available at our disposal.
This methodology won’t work if introspection is disabled. If you encounter this while testing, the only way is to go through each and every functionality available and observe requests and responses in Burp. You can also use some smart fuzzing techniques to identify endpoints if you have some knowledge of the application.
Examples with Demo App — Generic University
Now that we are done building the methodology, we will be using Generic University for demonstration.
Note: I have already created a docker of the application so you can set it on your system easily. Just follow the link below and you’re ready to deploy it in your environment.
There are list of challenges mentioned in the repository. We will try to solve few of them for demo purpose. You can find complete list here -
Goal: Brute force the API to find new endpoints
First thing’s first, let’s identify the endpoints available for us.
Runnig `dirb` tool with a payload list will give us some interesting endpoints to visit.
Let’s fuzz for GraphQL specific endpoint.
It seems we have found GraphiQL instance to play with.
Clicking buttons and links, we are able to identify the following directory structure.
Let’s run the introspection query and try to map everything using GraphQL voyager.
Once we got the visual representation, let’s see whether we can fetch some unauthenticated data.
If you check /admin endpoint for some sensitive data enumeration, it will redirect us to /home which is default page for application. Let’s keep this endpoint in mind and see whether we can use it in future.
Goal: Find out what grades everyone got in a class
Let’s start with querying some details.
As we can see we have details of class and we can ask for grades and user details associated with class using the above mentioned query. It turns out we found details of all class and grades of users, UNAUTHENTICATED.
A classic example of f SENSITIVE INFORMATION DISCLOSURE.
Goal: Edit someone’s grade
Now that we have enough data, let’s try to make changes to the grades. We need to use mutation type to do such things. Let’s check all available mutations.
We can see here, we can use gradeMutation to update grades. But before that, we need to query the grades so that we can identify which grade we want to change.
It looks like grade id 19 has the lowest score of 0. Let’s change that and make that user happy.
We have changed the grades of the user “Addie”. You can also see by passing the id, grade and other details, we have also asked for the changed data. We didn't have to query separately.
A classic example of IDOR.
Goal: Change another account’s password
If you have observed all the available mutations, you already have an idea what our next goal will be. Changing the password of another account. How about changing the password of the highest privilege user?
But for that we need to check what are the roles available to us and identify which users have the higher privileges. So let’s query users and check out what roles they have.
It seems we have found our victim. We will be changing the password of user id 6.
We just changed the password of an Admin account. Observe that when we queried for password, it sent us gibberish data in the password field. Which means they are using some mechanism to encrypt the password which can be saved in the database. Which is indeed a best practices for saving passwords in databases. Let’s verify by logging in with our brand new password.
And we are in.
A classic example of ADMIN ACCOUNT TAKEOVER.
Goal: Access admin API
Let’s check what we can explore in the admin account. Remember our dirb results? We identified an endpoint called /admin. Let’s try to access that.
Voila! We have access to the admin dashboard. Let’s see what security vulnerabilities we got.
And now we know the vulnerabilities exist in the portal.
Mitigation and Best Practices
We have exploited GraphQL but more importantly we need to look at mitigating these issues. Following are some of the mitigation points we can consider while developing applications using GraphQL.
- Use specific GraphQL data types such as scalars or enums. Write custom GraphQL validators for more complex validations.
- Define schemas for mutations input.
- Gracefully reject invalid input, make sure not to reveal excessive information about how the API and its validation works.
- Always use libraries/modules/packages offering safe APIs, such as parameterized statements.
- Use a well-documented and actively maintained escaping/encoding library.
- Add depth limiting to incoming queries
- Add amount limiting to incoming queries
- Add pagination to limit the amount of data that can be returned in a single response
- Add reasonable timeouts at the application layer, infrastructure layer, or both
- Consider performing query cost analysis and enforcing a maximum allowed cost per query
- Enforce rate limiting on incoming requests per IP or user (or both) to prevent basic DoS attacks
- Implement the batching and caching technique on the server-side (Facebook’s DataLoader can be used for this)
- Implement a proper access control mechanism to stop unauthorized actors from quering or modifying the data of other entity.
- Enforce authorization checks on both edges and nodes (see example bug report where nodes did not have authorization checks but edges did).
- Disable introspection queries system-wide in any production or publicly accessible environments. (This will make life hard for the attackers like us)
- Disable GraphiQL and other similar schema exploration tools in production or publicly accessible environments.
Hope you learned something new and enjoyed my blog. Stay safe, stay curious.
Thanks for reading!
Connect with me