GraphQL is a powerful query language for APIs that has gained popularity in recent years for its flexibility and ability to provide a great developer experience. However, with the rise of GraphQL usage comes the potential for security vulnerabilities and attacks.
In this blog post, we will describe what GraphQL is, why it is considered to be a preferred choice over REST API, describe and give examples of the different attacks you might experience on your GraphQL application, and recommend a few tips that will help strengthen your security.
In part two of this series, we will take a closer look at real-world GraphQL attacks that we observed.
What is GraphQL?
GraphQL is a query language for APIs that allows clients to request the exact data they need.
Here’s an example of a simple GraphQL query, where we are dealing with an API for managing a collection of books. In this example, we want to fetch the titles and authors of all books.
This query will return a list of all books with each book’s `title` and `author`.
The `author` field is a nested object that includes `firstName` and `lastName` fields.
GraphQL was designed to solve some of the limitations of REST, such as over- or under-fetching data. Unlike traditional REST APIs, which often return a fixed set of data, GraphQL allows clients to specify the exact fields and relationships they want in their response. This allows for more efficient data fetching and manipulation, and a better developer experience.
GraphQL vs REST
There are several reasons why developers might use GraphQL over REST for their APIs. One of them is data fetching efficiency.
In REST, the server provides a fixed set of endpoints, and the client makes multiple requests to these endpoints to retrieve all of the data needed for a single page, leading to a slow and inefficient user experience. In contrast, GraphQL provides a single endpoint that clients can use to request specific data. This way, the communication between the client and server is more efficient, as the client only receives the data it requires in a single request.
Let’s assume that our application has a view that displays user details: first name, last name, and age. With a REST API, you would probably go to /users/<id> endpoint to fetch the user details. Here is the response:
In this case, you got more data than needed. In contrast, a GraphQL API will only ask for the three desired fields:
Another reason might be GraphQL subscriptions. In GraphQL, subscriptions are a way to push data from the server to the client based on specific events, allowing real-time functionality. While queries fetch data and mutations change data, subscriptions establish a long-term connection to receive updates automatically.
This is how subscription works:
- The client subscribes to specific events.
- When those events occur, the server pushes the related data to the client.
- The client updates its state based on this new data.
Use cases for a developer to use subscriptions can vary from live chat applications, real-time feed updates like in a social media application, monitoring dashboard, etc.
There are additional points to consider, but they fall outside the scope of this blog post.
GraphQL API Vulnerabilities and Common Attack Examples
Like any technology, GraphQL is not immune to security threats and vulnerabilities.
Although GraphQL presents a fresh set of vulnerabilities for hackers to exploit, it remains crucial to maintain robust defenses against other prevalent web application threats, as these can still affect GraphQL traffic significantly. Refer to the section on “injection attacks” below for additional details.
Here is the list of what we are going to cover in this blog:
- Introspection Attack
- GraphiQL
- Excessive Errors/Fields Suggestions
- Denial of Service
- Injection
- Broken Authentication & Authorization
Introspection Attack
Before we can describe this type of attack, it is crucial to understand what the GraphQL Schema is. The schema is like a contract between the server and a client. It specifies the capabilities of the API and defines how clients can request the data. It contains all data types and their fields, and also all queries and mutation functions a client can request from the GraphQL API.
By default, almost all GraphQL instances come with the introspection system enabled which is used to ask a GraphQL schema for information about what queries it supports. This allows basically anyone to see the layout of the entire database.
Here is an example of how this query might look:
Introspection is not inherently a weakness, but it can be used and abused by attackers seeking information about a GraphQL implementation.
Security tip: Disable introspection in the production environment unless it’s necessary.
GraphiQL
GraphiQL (GraphQL interface) is a friendly integrated development environment-like (IDE) interface to construct queries and see the result in real time. You can start writing queries and it will auto-complete them or give you the documentation that describes the schema behind the GraphQL API. It is enabled by default in different GraphQL implementations, so attackers are likely to look for it.
It can be located under different paths such as: /graphiql, /playground, /v1/graphql, /v2/graphql, /console, etc.
Attackers will use it for information gathering and to put their hands on your API schema.
Security tip: The approach here is the same as in introspection – disable GraphiQL and other similar schema exploration tools in production or publicly accessible environments unless necessary.
Excessive Errors/Fields Suggestions
GraphQL tends to be incredibly verbose in its error messages. Attackers can use the information provided in the errors’ description to build a valid query/ attack.
Below, for example, the attacker will get hints to the valid available fields for “Author” type.
In most cases it is enabled by default and there is no way to disable it unless you patch it yourself.
Security tip: GraphQL APIs in production shouldn’t return verbose stack traces or be in debug mode. GraphQL owners should be using a middleware to control the errors the server returns. In addition, they should mask the errors and make them available to the developers but not the callers of the API.
Denial of Service
GraphQL APIs can be vulnerable to Denial of Service (DoS) attacks, where attackers send one or more requests to the API that overwhelm the application server due to the way that GraphQL works.
There are several types of DoS attacks which fall under this category.
Batching Attacks
GraphQL supports query batching, which takes a series of queries and sends them in one shot. This means that one HTTP request contains several GraphQL queries, while the process in the backend is one after the other.
This processing may lead to application-level DoS since a single network call is translated to a high number of queries or object requests. This might cause exhaustion of CPU or memory when the application communicates with the backend database and eventually make it unavailable to legitimate users.
Here for example, the attacker sends an array-based batching, by calling a heavy function called getHeavyData:
Attackers may abuse this capability for bypassing rate limit restrictions since in most cases the rate limit is counting the number of HTTP requests. Whereas, in this example, the attacker sends only a single request.
There are additional attack scenarios that could be relevant to batching attacks, including brute-force and object enumeration attacks, which target user names, emails, accounts, etc. Another risk posed by this technique is the potential to bypass one of the commonly used second factors of authentication, the One-Time Password (OTP). This can be done by sending all possible token variants in a single request.
Security tip: Disable batching queries in your application or set a limit with a reasonable number of queries.
Alias Overloading
If batching is disabled there is another way to overwhelm the server by sending the same query by using aliases.
An alias is used to give a different name to a field in a query or mutation. This can be useful in cases where you want to request the same field multiple times with different arguments or to avoid naming conflicts when querying multiple fields that have the same name.
Attackers can use this feature for batching, where one request will be sent over the network and then all queries will be executed sequentially.
Security tip: The alias is a useful feature, thus it will not be the best option to disable it. Instead, consider constraining the quantity of aliases your application permits. Another, more crude, approach could be to restrict the length of the HTTP request body. This approach would prevent an attacker from sending an excessively long request containing a large number of aliases.
Field Duplication
Field duplication is a type of attack where a malicious user sends a GraphQL query with duplicated fields to the server, causing the server to return multiple copies of the same field in the response. This can be used to overwhelm the server and cause it to consume excessive resources, leading to a denial-of-service (DoS) attack.
For example, consider the following GraphQL query:
In this query, the name and email fields are duplicated. When the server processes this query, it will return the name and email fields twice, leading to unnecessary computation and potentially overloading the server.
It might look like some implementations ignore the field duplication, since the response includes only one field, but the server still invests time and resources on processing and only removes the duplicate field from the result.
Security tip: To prevent this attack, GraphQL servers should validate the incoming queries and reject any that contain duplicate fields. Additionally, servers could limit the complexity of queries by setting a limit on the response time, to prevent excessive resource consumption.
Directives Overloading
In GraphQL, directives are used to provide instructions to the server on how to execute a query. A directive can be used to modify the behavior of a field or fragment, and it can be used multiple times within a query.
In this example, the email field has been overloaded with multiple directives, including some custom directives (@foo, @bar, @baz, etc.) that may not even exist on the server. This can cause the server to consume a significant amount of resources trying to process the query, potentially leading to a denial-of-service (DoS) attack.
Security tip: Limit the number of directives that can be used within a single query.
Circular Queries
In GraphQL, instances where types refer to one another can potentially lead to the creation of a circular query. Such queries can grow exponentially in complexity and size. If left unchecked, it may escalate to a level that could severely load the server or even cause it to crash.
A circular query, also known as a cyclic query, is a type of GraphQL query where the requested fields include a circular reference to another field, which can potentially cause the query to consume large amounts of resources leading to a denial-of-service (DoS) attack.
Here’s an example of a circular query:
In this example, we are requesting data for a ‘User’ identified by the ‘id’ 22, including their name and their list of friends. For each friend on that list, we are also asking for their name and their respective friends. This creates a cyclical reference that an attacker can abuse to construct a malicious query that can lead to a DoS attack.
Security tip: Developers can use schema visualization tools such as GraphQL Voyager to illustrate their schema’s interconnections, enabling them to identify potential object cross-references. In situations where a cycle is unavoidable, the implementation of depth limiting can serve as a guardrail against the overuse of resources.
Circular Fragments
Unlike circular queries, where the developer can prevent the attack by validating there are no circular relationships in the schema, when it comes to fragments the story is a bit different.
GraphQL fragments are essentially a set of fields that can be reused across multiple queries, mutations, or subscriptions. The use of fragments allows more complex queries to be built up from smaller, reusable pieces. They can also help to avoid redundancy in your queries, making your code easier to read and maintain.
Fragments are controlled only by the client which means the client can write whatever they want.
In the example below, the attacker wanted to create an infinite-loop by calling fragment X that calls fragment Y and vice versa. This will cause an infinitely recursive request, which could potentially cause the server to crash or significantly degrade its performance!
GraphQL servers are typically designed to detect such cycles and reject the query with an error message indicating a cyclic fragment reference, but there might be cases where the implementation isn’t spec-compliant.
Security tip: In general, we recommend using a GraphQL implementation that doesn’t allow recursive fragments. Check if your GraphQL server prevents these cycles. If it doesn’t, then it’s important to add some tests of cycle detection to prevent potential denial of service attacks or other performance issues.
Pagination Limit Bypass
Pagination limit bypass is a type of attack in which an attacker attempts to bypass the limits set by pagination controls in server-side GraphQL implementations. This attack occurs when an attacker manipulates the pagination parameters in a GraphQL query to bypass the intended page limits and retrieve more data than intended by the server. It can lead to excessive resource consumption, slow response times, and potentially expose sensitive or private information.
There are several known pagination controls such as “first”, “last”, “before”, and “after” to retrieve more data than intended which an attacker can manipulate. For example, an attacker may try to set the “first” parameter to a very high value or use the “after” cursor to access pages that should be inaccessible.
Let’s assume we have an application that displays user details along with their first 50 friends.
In the following scenario, the attacker crafted a query that overrides the server’s configured limit with the aim to retrieve a larger number of friends or even all friends associated with the user.
In this case, the ‘first’ variable is set to 10,000, which is much higher than the server’s configured limit for the maximum number of friends that can be returned in a query. This could cause the server to consume a significant amount of resources trying to process the query, potentially leading to a DoS attack.
Security tip: To prevent this attack, it’s important to implement proper validation and sanitization of pagination parameters in a GraphQL query. In addition, the API should enforce strict limits on the number of items that can be requested in a single page or implement rate-limiting mechanisms.
Injection
Injection attacks in GraphQL are a type of security vulnerability that allow an attacker to inject and execute arbitrary code or commands within a GraphQL API. These attacks exploit vulnerabilities in the way user-supplied input is handled by the GraphQL server.
SQL Injection
GraphQL SQL injection (SQLi) attacks are a type of security vulnerability in GraphQL APIs that allow an attacker to execute malicious SQL queries against a backend database. In GraphQL, SQLi attacks occur when user-supplied input is not properly sanitized or validated before being used in a database query. Attackers can exploit this vulnerability by injecting malicious SQL code into a GraphQL query, which is then executed by the backend database.
This type of attack can be used to bypass authentication and authorization controls, execute arbitrary SQL queries, and access or modify sensitive data.
Here’s an example of a vulnerable GraphQL query:
In this example, the attacker has injected a malicious SQL statement into the ‘id’ parameter of the ‘user’ query. The SQL statement “1′ OR ‘1’=’1” will always evaluate to true, effectively bypassing any checks that might be in place to prevent unauthorized access. This could allow the attacker to retrieve sensitive data or perform other malicious actions.
Security tip: Located after “OS Command Injection”.
Cross-Site Scripting
Cross-site scripting (XSS) attacks in GraphQL occur when user-supplied input is not properly sanitized or validated and the output of the response is getting reflected on the screen on a web page. Both reflected and stored XSS are possible depending upon the vulnerable operation used (query/mutation).
Reflected XSS occurs when an application takes user input and immediately sends it back to the browser without properly sanitizing it first. For example, if a GraphQL query operation takes user input and directly includes it in the error messages or other parts of the response that are rendered in the end user’s browser, an attacker could craft a malicious input that results in arbitrary script execution in the victim’s browser when the response is rendered.
Stored XSS (also known as persistent XSS) is a more dangerous variant where the injected script is permanently stored on the target server (for example, in a database), and then served as part of a webpage to users. In the context of GraphQL, this might occur if a mutation operation is used to store user input on the server, and this input is not properly sanitized before being stored and subsequently served to users.
Here’s an example of a malicious query that injects a script tag into the ‘biography’ field:
When this query is executed, the script tag is stored in the database as part of the author’s biography. Later, when the original GraphQL query is executed to fetch the author’s name and biography, the script tag is included in the response and executed in the victim’s browser, displaying an alert box with the message “XSS attack!”.
Security tip: Located after “OS Command Injection”.
OS Command Injection
OS command injection is a security vulnerability that allows an attacker to execute arbitrary commands on the server where the application is hosted. It occurs when an application includes unsanitized user-supplied input in a command that is passed to the operating system (OS) for execution.
In a GraphQL context, an OS command injection vulnerability could occur if you have a mutation or query that takes user-supplied input and uses it in a system command.
For instance, let’s say the server has a functionality where it fetches an avatar image from a provided URL to store it locally or perform some kind of processing. If it uses some kind of shell command to do this, like Wget or cURL, and directly inserts the user-provided avatarUrl into the command string without validation or sanitization, then the command injection could happen.
Here is an example of a GraphQL query that may lead to a command injection:
When this query is executed, the script tag is stored in the database as part of the author’s biography. Later, when the original GraphQL query is executed to fetch the author’s name and biography, the script tag is included in the response and executed in the victim’s browser, displaying an alert box with the message “XSS attack!”.
Security tip: Located after “OS Command Injection”.
Server-Side Request Forgery
Server-Side-Request-Forgery (SSRF) is a type of attack where an attacker can manipulate a web application to make HTTP requests on their behalf, potentially accessing internal systems that are not intended to be publicly accessible.
In the context of GraphQL, SSRF attacks can occur when an attacker sends a malicious GraphQL query or mutation that includes a URL as a variable or argument, which can then be used to make an unauthorized HTTP request to a vulnerable server or system.
Here’s an example of a GraphQL mutation that could potentially be vulnerable to SSRF attack:
In this example, the ‘fetchUrlContent’ mutation accepts a ‘url’ argument, makes an HTTP request to that URL and returns the content. If the server doesn’t properly validate the ‘url’ argument, an attacker could provide a URL to an internal service that shouldn’t be accessible externally, potentially accessing sensitive data or even executing commands if the internal service is poorly secured.
Security tip: To prevent SSRF attacks in GraphQL, it’s important to properly validate and sanitize any user input, including URL variables or arguments. Additionally, GraphQL APIs should implement strict server-side validation and filtering to ensure that only authorized requests are executed. This can be done by using an allow-list of trusted domains or IP addresses to prevent attackers from making requests to internal or sensitive resources.
Broken Authentication & Authorization
Authorization and authentication are two related but distinct concepts. Authentication is the process of verifying the identity of a user, while authorization is the process of determining whether a user has the necessary permissions to perform a certain action.
Authentication Bypass
Authentication in GraphQL is not handled by GraphQL itself, but instead it is implemented by the developer on the server-side. This could involve a variety of methods such as: session-based authentication, token-based authentication (like JWT), etc.
Let’s assume that we have a ‘login’ mutation in our GraphQL API that looks something like this:
The intended usage is that a client sends this mutation with the correct username and password, and in return, they get a token to authenticate further requests.
An attacker might attempt a brute force attack, via GraphQL batching feature to send multiple queries in one HTTP request, to guess the password, and by receiving a token, permitting them full access to the API as an authenticated user.
Security tip: To prevent authentication attacks on GraphQL APIs, it’s crucial to implement secure session management, enforce robust password rules, and implement measures against brute force and credential stuffing attacks like rate limiting or monitor leaked credentials. Additionally, consider multi-factor authentication (MFA) and always validate, sanitize, and encrypt sensitive data.
Authorization
Authorization in GraphQL involves determining what data and operations a user is allowed to access and perform, once their identity has been authenticated. Just like authentication, GraphQL does not provide built-in authorization mechanisms. Instead, authorization logic is implemented on the server-side by the developer.
Authorization can be done in various ways in a GraphQL API such as schema or directives level, business logic, by using a middleware etc.
Consider a GraphQL API where users have different roles such as “employee”, “manager”, and “admin”. Each role has different levels of access to sensitive data.
Each “employee” has permission to fetch their own details. A typical query might look like this:
In this specific case, an attacker might try to manipulate the “id” value and access details of other users. This type of application business logic vulnerability, known as Broken Object Level Authorization (BOLA), fails to enforce proper authorization checks at the object or resource level.
Another example is the following query, which is designed to retrieve a list of all users and their associated details, including the sensitive ‘salary’ field.
In a well-designed system, only users with privileged roles such as a “manager” or “admin” should have the ability to execute this query.
Unfortunately, if the API lacks proper authorization checks, a user with a lower privileged role like “employee” may exploit this vulnerability and execute the query, resulting in unauthorized access to sensitive data. In this type of application business logic vulnerability, known as Broken Function Level Authorization (BFLA), the application fails to enforce proper authorization checks at the function or operation level.
Security tip: To prevent authorization attacks in GraphQL, developers should follow the principle of least privilege, applying fine-grained access controls on a per-field basis. Include authorization checks in the business logic and regularly audit these controls. Schema directives for authorization can also add an extra layer of validation for user roles and permissions before resolving certain fields.
Conclusion
While GraphQL offers many benefits for modern web applications, it’s important to be aware of the potential cyber threats that it can introduce.
By understanding the various types of attacks that can occur, such as batching attacks and injection attacks, and implementing appropriate security measures such as input validation, access control, and rate limiting, defenders can help mitigate the risk of these threats. Staying up-to-date with the latest security trends and best practices, while regularly testing and monitoring GraphQL APIs for vulnerabilities, can help ensure that web applications using GraphQL remain secure and protected against cyber attacks.
Imperva Web Application and API Protection (WAAP), which includes API Security capabilities, provides protection against conventional web application threats, including injection attacks, but also adds an extra layer of security with dedicated inspection and ability to protect applications that use GraphQL APIs.
Try Imperva for Free
Protect your business for 30 days on Imperva.