How to make your GraphQL Implementation more secure

Many developers love GraphQL because it uses one endpoint and enables clients to customize their requests to get the required information sets. However, when it involves security, GraphQL’s flexibility can introduce specific security vulnerabilities that each developer must consider. When a number of our developers began using GraphQL at Nord Security, it absolutely was my job as a part of the core web security team to research the language’s potential vulnerabilities. Hopefully, the issues and fixes we found will help further make your implementation safer. For your Complete GraphQL Security and Management Platform visit inigo.io

1. Aim for secure configurations 

Many GraphQL implementations have potentially insecure settings enabled by default that expand the application’s attack surface. 

To introspect or to not introspect

Introspection could be a powerful feature in GraphQL that’s wont to retrieve information about the queries, types, fields, and mutations it supports. it’s extremely useful for clients that consume GraphQL endpoints because it provides comprehensive documentation and makes integration lots easier. However, this comes with a price. This functionality may be abused to retrieve GraphQL schema, which could help attackers search systems for vulnerabilities.

Having GraphQL introspection in production is typically not considered a vulnerability by itself. Sometimes it is wise to possess it if the API is public and is supposed to be employed by developers outside of your organization. for personal APIs, however, it’d expose sensitive information and expand the attack surface. A rule of thumb is to disable introspection in production unless you’ve got an honest reason to not. Even during this case, consider allowing introspection just for authorized requests.

In addition to introspection, developers can use GraphiQL (GraphQL IDE), which offers a user-friendly interface to explore the GraphQL schema. While it’s useful in development, it’ll pose the identical risk as introspection if left in production, so confirm it’s disabled.

Make fuzzing fields and queries harder

Even when introspection is disabled, attackers will try and fuzz field and query values to seek out sensitive operations which will be exploited further. GraphQL makes this task easier with its built-in Field Suggestions feature. When a typo is formed within the field name, GraphQL will try and suggest an existing field that’s the same as the one requested.

“errors”:[
{
“message”:
“Cannot query field “prices” on type “Plan”. Did you mean “price”?”,
“locations”:[
{
“line”:1,
“column”:52
}
],
“extensions”:{
“code”:”GRAPHQL_VALIDATION_FAILED”
}
}
],
“data”:null

This is a generic error returned from a GraphQL endpoint when trying to request a field (prices in this case) that does not exist in the schema. The GraphQL field suggestion feature shows that the actual field name is price.

It can be used to enumerate GraphQL schemas and reveal sensitive field names. Therefore, if your GraphQL implementation supports it, you should consider disabling this feature for private APIs.

beware of excessive errors

GraphQL’s detailed errors help developers accurately identify issues in development. However, default error messages are too verbose and can pose a security risk by exposing sensitive information about your backend infrastructure and exposing vulnerable implementations. That’s why you should never run GraphQL in debug mode in production. Instead, have a middleware to consume those verbose errors and stack trace, log them securely, and return user-friendly errors to end users.

2. Protect against Denial of Service Attacks

One of the most common ways to mitigate application-layer DoS attacks is to limit requests to API endpoints from a specific IP address. This prevents the API from being clogged with requests, thus draining system resources and making them inaccessible to normal users. While request rate limiting is also important for GraphQL endpoints and should be implemented, it will rarely be enough to prevent DoS attacks. With GraphQL, a well-crafted query can bring down an entire server. Therefore, you need additional security measures to ensure that your system remains available.

Apply max depth check

The flexibility of GraphQL allows clients to formulate complex and nested queries, thus obtaining the precise data sets they need to obtain. The problem, however, is that many GraphQL schemas contain cyclic graphs that can be misused in malicious queries. Let’s look at a deeply nested example query:

query circularQuery {
product {
plans {
product {
plans {
product {
# and so on…
}
}
}
}
}
}

Each new nesting level exponentially increases the response size, so a sufficiently deep query can deplete your server resources and make it unavailable. You can prevent this by limiting the depth of the GraphQL query. This would require a custom implementation as it is generally not supported natively by GraphQL. Set the depth to a reasonable number that won’t cause difficulties for your API users:

“errors”:[
{
“message”:”operation has depth 19, which exceeds the limit of 6″,
“extensions”:{
“code”:”DEPTH_LIMIT_EXCEEDED”
}
}
],
“data”:null

Example of nesting protection implemented in the GraphQL API. The error shows that the query was rejected because its depth limit was exceeded.

Consider query complexity

Having nesting security may not be enough to prevent complex queries from causing security problems. Some fields may return more nodes than others, making them computationally expensive. This is where query cost analysis comes into play. Specify cost values ​​for different fields and reject the query if the complexity limit is exceeded:

“errors”:[
{
“message”:”operation has complexity 312, which exceeds the limit of 160″,
“extensions”:{
“code”:”COMPLEXITY_LIMIT_EXCEEDED”
}
}
],
“data”:null

An error returned from the GraphQL API in which query complexity checking has been implemented indicates that the query sent in the request exceeds the allowed complexity and was therefore rejected.

Query complexity checking usually takes more effort to implement and may not be needed in all cases. Before choosing this route, evaluate the complexity of your GraphQL schema and decide if this containment is really necessary.

Watch out for batching attacks

GraphQL lets users send multiple queries in a single request. This is called query batching, and despite its many benefits, it can also allow hard-to-detect exploits. This can result not only in DoS attacks, but also in object enumeration, brute force attacks, and rate limit bypasses. WAF (Web Application Firewall) or IDS (Intrusion Detection System) cannot detect these attacks because they will only result in a single request like this:

mutation {
a1: login(username: “test user”, password: “graphql1”) {
token
}
a2: login(username: “test user”, password: “graphql2”) {
token
}
a3: login(username: “test user”, password: “graphql3”) {
token
}
#….
}

This is an example of query batching that can be used for a password brute-force attack.

Some mitigation techniques may include limiting the number of operations that can be batched and preventing batching for sensitive items.

cache your data

While the above techniques will solve some of the typical security issues of GrapeQL, there may be situations when this will not suffice. This could either be a bug in the implementation or a common API usage scenario that you didn’t think about. Consider the example given below:

query plans (product: [NordVPN, NordVPN, NordVPN, NordVPN….]) {
id
price
currency
}

The same field value is passed multiple times, resulting in a large number of DB queries. To prevent these kinds of problems from happening, consider implementing caching for your queries. This will prevent DoS attacks and increase the efficiency of your GraphQL API by limiting resource consumption.

Note other API vulnerabilities

While GraphQL has its own specific security issues that need to be addressed, it is no different from other technologies in terms of API security. It is also prone to injection, IDOR, authentication and authorization failures, and other common security vulnerabilities. It is important to follow secure development practices and perform proper security testing before deploying your API to production.

don’t forget to monitor

Monitoring your system is always important and GraphQL Endpoint is no exception. Even the most thorough implementation can cause problems, so keep an eye out for:

  • Query performance. Are there any signs of slow queries that need to be optimized?
  • Significant response delays. How long is it taking for the endpoint to respond?
  • Errors and failures. Is there any validation or query parsing errors or failures in the database?

Monitoring your GraphQL endpoint will not only help identify security issues but also identify any potential bottlenecks in your data fetch pipeline so that you can optimize your endpoint for better performance.

Editor’s note: This article was written by a leading cybersecurity professional at Nord Security. His identity has been withheld at his discretion.

Leave a Comment

x