GraphQL has emerged as a versatile API query language, gaining widespread popularity due to its flexibility and developer-friendly features.
Yet, as its adoption grows, it also brings along the need for heightened security awareness, given the potential for security vulnerabilities and attacks in “GraphQL-powered applications”.
This blog explains the importance of GraphQL, and its preference of choice between REST APIs. We will also look through the common vulnerability attacks in GraphQL.
GraphQL is a query language for APIs and a runtime for executing those queries using your current data.
GraphQL offers a comprehensive and easily comprehensible data representation for your API.
It empowers clients to request precisely the data they require, streamlining API evolution, and facilitating the creation of robust developer tools.
There are several reasons why developers may opt for GraphQL instead of REST for their APIs, and one of them revolves around enhancing data retrieval efficiency.
In a RESTful architecture, the server exposes a fixed set of endpoints, and clients must make multiple requests to these endpoints to gather all the necessary data for a single page.
This approach can result in a sluggish and inefficient user experience.
In contrast, GraphQL offers a single, versatile endpoint that clients can utilize to request precisely the data they need.
It also streamlines communication between the client and server, as the client receives only the required data in a single request.
Another compelling reason to favor GraphQL is its support for subscriptions.
In GraphQL, subscriptions serve as a mechanism for real-time data delivery from the server to the client, triggered by specific events.
This feature empowers real-time functionality within applications.
While queries retrieve data and mutations modify data, subscriptions establish persistent connections to automatically receive updates.
Here’s how subscriptions operate:
The client subscribes to specific events.
When these events occur, the server proactively sends the relevant data to the client.
The client updates its state based on the newly received data.
Developers can find various use cases for subscriptions, ranging from live chat applications and real-time feed updates, as seen in social media apps, to monitoring dashboards, and more.
It’s worth noting that there are additional aspects to explore, but they extend beyond the scope of this blog post.
Similar to all technologies, GraphQL is not impervious to security threats and vulnerabilities.
While GraphQL introduces a unique set of potential vulnerabilities that malicious actors can exploit, it remains imperative to uphold strong defenses against other well-established web application threats.
These threats can still have a significant impact on the security of GraphQL traffic.
Before moving into this type of attack, it’s essential to grasp the concept of the GraphQL schema.
The schema acts as a contractual agreement between the server and a client, outlining the APIs capabilities and dictating how clients can request data.
It defines all data types, their respective fields, as well as the available queries and mutation functions that clients can employ to interact with the GraphQL API.
In most cases, GraphQL instances have the introspection system enabled by default.
This system enables clients to query the GraphQL schema for information regarding the supported queries.
It provides insight into the entire database’s structure, making it accessible to virtually anyone.
Introspection is not a weakness but can be used by the attacker’s seeking information about a GraphQL implementation.
GraphiQL, which serves as a user-friendly Integrated Development Environment (IDE) for GraphQL, offers an intuitive interface for constructing queries and immediately viewing the results.
As you begin to compose queries, it provides autocompletion and readily offers schema documentation to explain the GraphQL API’s structure.
This feature is often enabled by default in various GraphQL implementations, making it a potential target for attackers.
GraphiQL can be accessed through various paths, including /graphiql, /playground, /v1/graphql, /v2/graphql, /console, and more.
Malicious actors may leverage GraphiQL for information gathering and gaining insights into your API schema.
GraphQL often produces highly detailed error messages, which can be exploited by attackers to craft valid queries or launch attacks.
For instance, these error descriptions can provide attackers with valuable insights into the accessible fields for a specific “Author” type.
It is enabled in most cases and there is no way out unless you patch it yourself.
GraphQL APIs are susceptible to Denial of Service (DoS) attacks. Attackers may launch one or multiple requests against the API, causing an overload on the application server.
This occurs due to the inherent working of GraphQL.
This category encompasses several types of DoS attacks.
GraphQL offers the capability of query batching, allowing multiple queries to be bundled into a single HTTP request.
While the backend processes these queries sequentially, the result may be an application-level Denial of Service (DoS) risk.
With one network call translating into numerous queries or object requests, the application’s communication with the backend database could lead to CPU or memory exhaustion, potentially rendering it inaccessible to genuine users.
Example: When an attacker sends an array-based batching by calling a heavy function
Attackers can potentially exploit this feature to circumvent rate-limiting measures, which often count the number of HTTP requests made.
In the scenario described, the attacker sends only a single request, allowing them to evade rate limits.
Batching attacks introduce additional attack vectors, such as brute-force and object enumeration attacks that target user information like names, emails, and accounts.
Another concerning risk associated with this technique is the ability to bypass one of the widely used second factors of authentication, the One-Time Password (OTP).
Attackers can achieve this by sending all possible token variations in a single request.
Even when batching is disabled, attackers have an alternative method to potentially overwhelm the server by exploiting the use of aliases in GraphQL.
Aliases in GraphQL are employed to assign different names to fields within a query or mutation.
They serve practical purposes such as requesting the same field multiple times with distinct arguments or resolving naming conflicts when querying multiple fields sharing the same name.
Attackers can leverage aliases to achieve a form of batching, where a single request is transmitted over the network, and then all the queries within it are executed sequentially.
Field duplication represents a type of attack in which a malicious user transmits a GraphQL query containing duplicated fields to the server.
This action prompts the server to deliver multiple copies of the same field in the response.
This tactic can be employed to overload the server, pushing it to consume excessive resources, ultimately resulting in a denial-of-service (DoS) attack.
In the query below, there are duplications of the “name” and “email” fields. As the server handles this query, it results in the delivery of these fields twice, which can impose unnecessary computational burdens and potentially strain the server’s resources.
While it might appear that some implementations overlook field duplications since the response seemingly includes only one instance of each field, it’s important to note that the server still dedicates time and resources to process these duplications, even if it ultimately eliminates the redundant fields from the result.
In GraphQL, directives serve as tools to furnish the server with instructions on how to execute a query.
These directives can be employed to alter the behavior of a field or a fragment, and they have the flexibility to be applied multiple times within a single query.
In GraphQL, situations where types refer to each other can give rise to the development of a circular query.
These queries have the potential to expand significantly in terms of complexity and size. If unmonitored, they may escalate to a level where they place substantial strain on the server or even trigger a server crash.
A circular query, also referred to as a cyclic query, is a specific type of GraphQL query.
In such queries, the requested fields contain circular references to other fields, potentially resulting in the query consuming substantial resources, thereby paving the way for a denial-of-service (DoS) attack.
In contrast to circular queries, where safeguarding against attacks involves schema validation to eliminate circular relationships, GraphQL fragments offer a different dimension of utility.
GraphQL fragments serve as a collection of fields that can be applied across multiple queries, mutations, or subscriptions.
These fragments empower developers to assemble sophisticated queries from modular, reusable elements.
Additionally, fragments streamline query construction by minimizing redundancy, resulting in more readable and maintainable code.
Their versatility and code optimization capabilities make fragments an indispensable tool in GraphQL development.
Fragments are entirely under the control of the client, granting clients the freedom to compose fragments according to their specific needs and preferences.
GraphQL servers are commonly structured to identify such cycles and decline the query, generating an error message that highlights a cyclic fragment reference. However, there could be instances where the implementation deviates from specification compliance.
Pagination limit bypass is a form of attack in which an assailant seeks to circumvent the restrictions established by server-side GraphQL implementations regarding pagination.
This attack transpires when the attacker manipulates the pagination parameters within a GraphQL query, surpassing the server’s intended page limits and acquiring more data than originally authorized.
Such actions can result in heightened resource utilization, protracted response times, and the potential exposure of sensitive or private information.
Several well-recognized pagination controls, including “first,” “last,” “before,” and “after,” can be exploited by an attacker to acquire more data than initially intended.
Injection attacks in GraphQL represent a category of security vulnerabilities that enable attackers to insert and execute arbitrary code or commands within a GraphQL API.
Such attacks take advantage of weaknesses in how the GraphQL server manages user-supplied input.
GraphQL SQL injection (SQLi) attacks denote a form of security vulnerability within GraphQL APIs that enable attackers to execute harmful SQL queries against a backend database.
These attacks transpire when user-provided input is inadequately sanitized or validated before incorporation into a database query.
Exploiting this vulnerability, attackers insert malicious SQL code into a GraphQL query, which the backend database subsequently executes.
Such attacks have the potential to bypass authentication and authorization controls, perform arbitrary SQL queries, and gain access to or manipulate sensitive data.
Consider a GraphQL API with a ‘pastes’ query that fetches content and titles based on a specified filter. Initially, without a payload in the filter, the query returns an empty array:
However, when an attacker injects a malicious SQL payload into the filter. The SQL statement “1′ OR ‘1’=’1” will always be evaluated to be 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.
Cross-site scripting (XSS) attacks within the realm of GraphQL arise when user-supplied input is mishandled, lacking proper sanitization or validation, and the resulting response content is displayed on a web page.
The susceptibility to XSS can result in both reflected and stored exploits, contingent on the specific operation in question (query or mutation).
Reflected XSS unfolds when an application directly incorporates user input into its response without first applying appropriate sanitization.
For instance, if a GraphQL query operation takes user input and inserts it into error messages or other parts of the response, subsequently rendered in the end user’s browser, an attacker can contrive malicious input that leads to arbitrary script execution in the victim’s browser when the response is displayed.
Stored XSS, also known as persistent XSS, is a more perilous variant where the injected script is enduringly retained on the target server, like within a database, and then dispensed as part of a webpage to users.
Within the context of GraphQL, this could occur when a mutation operation is employed to store user input on the server.
If this input isn’t correctly sanitized prior to storage and subsequent delivery to users, it can be used to perpetrate stored XSS attacks.
Consider the following GraphQL mutation where an attacker injects XSS payloads into the “title” and “content” parameters:
In this example, the attacker aims to store malicious scripts within the “title” and “content” fields.
When the GraphQL query is executed to retrieve the “pastes,” the stored script tags are included in the response. Subsequently, these scripts are executed in the victim’s browser, triggering an alert box with the message “XSS.”
OS command injection is a security vulnerability that empowers an attacker to execute arbitrary commands on the server hosting the application.
This vulnerability emerges when an application incorporates user-supplied input without proper sanitization into a command executed by the operating system (OS).
In the context of GraphQL, an OS command injection vulnerability may manifest if a mutation or query accepts user input and employs it within a system command.
For instance, consider a scenario where the server offers a feature to retrieve an avatar image from a provided URL for local storage or processing.
If the server employs shell commands like Wget or cURL and directly incorporates the user-supplied “avatarUrl” into the command string without adequate validation or sanitization, it becomes susceptible to command injection.
Consider the following GraphQL mutation where an attacker attempts to exploit the “path” parameter by injecting an arbitrary command:
In this example, the attacker injects the command “/ ; uname -a” into the “path” parameter, potentially leading to the execution of arbitrary operating system commands. The GraphQL API, when processing this mutation, may inadvertently execute the injected command, exposing sensitive system information.
Server-Side Request Forgery (SSRF) is a form of attack enabling an attacker to manipulate a web application, compelling it to execute HTTP requests on their behalf.
This action may grant unauthorized access to internal systems that were not intended for public accessibility.
In the context of GraphQL, SSRF attacks can materialize when an attacker submits a malevolent GraphQL query or mutation.
This input may include a URL as a variable or argument, potentially enabling the execution of unauthorized HTTP requests against a susceptible server or system.
Consider the following GraphQL mutation where an attacker attempts to exploit the “host,” “port,” and “path” parameters to perform SSRF:
In this scenario, the GraphQL API fetches data from the manipulated URL, exposing the response from the SSRF target.
This demonstrates the potential risks associated with SSRF vulnerabilities, as attackers can use such flaws to probe internal network resources and gather sensitive information.
Authentication and authorization are interrelated yet separate principles.
Authentication verifies a user’s identity, while authorization determines if the user possesses the required permissions for a particular action.
Authentication within GraphQL is not inherently managed by GraphQL itself; instead, it falls under the responsibility of the server-side developer.
Various methods can be employed for authentication, including session-based authentication and token-based authentication (such as JWT).
The intended operation involves a client submitting this mutation with the accurate username and password. In response, they receive a token that allows them to authenticate subsequent requests.
However, an attacker could potentially launch a brute force attack using the GraphQL batching feature, where multiple queries are consolidated within a single HTTP request.
Their aim is to make repeated attempts at guessing the password. If they succeed and obtain a token, this will grant them unrestricted access to the API as an authenticated user.
Authorization in GraphQL pertains to the process of establishing the data and actions a user is permitted to access and execute, subsequent to their authentication.
Similar to authentication, GraphQL does not supply inherent authorization mechanisms. Rather, developers implement authorization logic on the server-side.
In a GraphQL API, authorization can be executed through several methods, including at the schema or directives level, by means of business logic, or by employing middleware.
For instance, take a GraphQL API in which users are categorized into distinct roles, such as “employee,” “manager,” and “admin.”
Each role is associated with varying degrees of access to sensitive data. It’s essential to implement authorization at the schema level to thwart IDOR vulnerabilities. Consider a scenario where users can fetch their own project details:
Here, the “id” parameter is set to “10” to retrieve details of the authenticated user’s assigned project. However, manipulating the “id” parameter could pose a security risk, allowing an attacker unauthorized access to other projects—an instance of Broken Object Level Authorization (BOLA).
To mitigate this risk, developers should enforce stringent authorization checks at the schema level to ensure that users can only access details related to their own projects.
Imagine an another GraphQL query designed to retrieve confidential client information, including transaction details:
In a secure system, only privileged roles like “financial_manager” or “admin” should execute this query.
Without proper authorization checks, a user with a lower privileged role, like a “customer_service_rep,” may exploit this vulnerability, leading to unauthorized access to sensitive financial data,
To address this, developers should implement fine-grained access controls on a per-field basis.
By enforcing stringent authorization checks at the function or operation level, developers can adhere to the principle of least privilege, ensuring that users can only access data for which they are authorized.
While GraphQL offers numerous advantages for contemporary web applications, it’s crucial to remain vigilant regarding the potential cybersecurity risks it may introduce.
By gaining insight into the diverse array of attacks, such as batching attacks and injection attacks, and by implementing appropriate security measures like input validation, access control, and rate limiting, defenders can effectively reduce the risk associated with these threats.
Remaining updated on the latest security trends and adhering to best practices, along with regular testing and continuous monitoring of GraphQL APIs for vulnerabilities, are essential for ensuring the security of web applications using GraphQL, shielding them against cyber threats.