Skip to main content
BlogComputeDefend Your GraphQL Server Against Excessive Resource Consumption

Defend Your GraphQL Server Against Excessive Resource Consumption

Defend_Your_GraphQL_Server_Against_Excessive_Resource_Consumption (2)

GraphQL offers the flexibility to request exactly the resources and attributes you need—nothing more, and nothing less. This is unlike REST, which often over-fetches or under-fetches data. This precision makes GraphQL highly efficient. Additionally, GraphQL offers standard ways to handle pagination, further enhancing its flexibility. However, malicious users can exploit these GraphQL features, and that can pose significant risks to your server’s stability.

In this blog, we’ll explore how GraphQL’s flexibility can be turned against you. We’re specifically focusing on a vulnerability highlighted by the OWASP API Security Top 10: unrestricted resource consumption. We’ll also discuss practical steps to protect your systems from this kind of abuse.

GraphQL’s Answer to Over-fetching and Under-fetching

GraphQL was designed in part to address the over-fetching problems we normally encounter with REST. For example, let’s consider a standard ecommerce database with users, products, and orders. Imagine needing to fetch the total dollar amount of an order given in order ID. With REST, that GET request to /orders/17 would fetch not just the dollar amount, but also the following:

  • order data
  • billing information
  • shipping information
  • product quantities
  • tax
  • order status
  • … and more

That’s over-fetching.

With REST, you also encounter under-fetching, when you need to piece together associated data from multiple resources. Imagine you want to display a summary, with the date and status of an order, the names and descriptions of all the products in the order, and the name and email of the user who placed the order. To do this, you would need to send multiple requests:

  • GET /orders/17
  • GET /products/1662527
  • GET /products/9914188
  • GET /products/3750021
  • GET /products/7557109
  • GET /products/6081142
  • GET /users/3314

GraphQL was the answer to these REST shortcomings. You can query for exactly what you need, across associated resources. To accomplish the above, your GraphQL might look like this:

query {
  order(id: "17") {
    date
    status
    products {
      name
      description
    }
    user {
      name
      email
    }
  }
}

It’s so simple and flexible! However, this level of flexibility also means a malicious user can deliberately over-fetch data.

Abusing query flexibility

The querying syntax for GraphQL allows you to perform multiple queries within the same request. We could take the above query and do something like this:

query {
  Q1: order(id: "17") {
    date
    status
    products {
      name
      description
    }
    user {
      name
      email
    }
  }
  Q2: order(id: "17") {
    date
    status
    products {
      name
      description
    }
    user {
      name
      email
    }
  }
}

This time, we requested the same data as the previous query, except we requested it twice. Naturally, the response would be double the size of the single-query request. However, what if we repeated the query 100 times in a single request?

query {
  Q00: order(id: "17") {    …  }  Q01: order(id: "17") {    …  }  …  Q99: order(id: "17") {    …  }
}

Just like that, we’ve crafted a query that would yield a response 100 times the size of our original query. A good way to illustrate this attack is with the following image of cheeseburgers. The hamburger buns form the single request; but in between the buns, you could have hundreds of beef patties!

stuffed query

If a malicious user were to abuse this querying flexibility, the excessive resource consumption could choke your server.

Abusing pagination features

GraphQL also has some standard ways to handle pagination. A common approach is offset-based pagination, in which the caller provides the number of items to fetch (limit) and the item to start with (offset).

For example, a query to return information from a product search might look like this:

query {
  products(searchString: "shirt", limit: 8, offset: 0) {
    id
    name
    description
    sku
    category
    images {
      path
      alt_text
    }
    variations {
      name
      description
      cost
    }
  }
}

What if we adjusted the pagination parameters to run this query instead?

query {
  products(searchString: "shirt", limit: 800, offset: 0) {    …  }}

In a similar set of examples I tested on an ecommerce site, I compared the responses I received:

$ du response*.json
680496  response_big.json
333649  response_small.json

You might think the large file would be roughly 100 times the size of the small file since the limit was set to 800 instead of 8. The search likely didn’t yield 800 results overall (possibly only 16 to 20).

Abusing pagination is another example of how someone might manipulate GraphQL query parameters to add load to your server. By exploiting this feature, attackers can force your server to handle much larger requests than intended, potentially leading to resource exhaustion and denial of service (DoS).

OWASP API4:2023 Unrestricted Resource Consumption

What we’ve been demonstrating falls under the OWASP API Security Top 10, specifically API4:2023 Unrestricted Resource Consumption. This vulnerability occurs when APIs don’t limit certain kinds of interactions or requests, leaving the server vulnerable to a DoS attacks and other operational disruptions.

When an API allows excessive querying or pagination without boundaries, it can lead to spiraling resource consumption. This in turn can cause performance degradation or even complete server failure. Without proper limits in place, these attacks can disrupt service. You could incur high operational costs, see customers walk away, and lose business.

How to protect yourself

To deal with these risks, you need to properly regulate API requests. Implement controls that restrict the amount of data that can be requested. This includes setting limits on the size and number of queries, as well as implementing rate limiting and other protective measures.

Here are some practical suggestions for protecting yourself:

  • Request and response payload size limits: By setting limits on the size of the data that can be requested and returned, you can prevent overly large queries from overwhelming your server.
  • Pagination boundaries: Implement a max limit for pagination, ensuring a caller does not attempt to fetch more records than is reasonable.
  • Web Application Firewall (WAF): A WAF can help detect and block malicious activity, thwarting potential attacks on your GraphQL API. Consider using a WAF like Haltdos from the Linode Marketplace to add this extra layer of security.
  • API security tools: Tools that can detect and mitigate malicious activity are essential. They can help identify unusual patterns that might indicate an attack, allowing you to take action before it affects your system.

By taking these steps, you can significantly reduce the risk of exploiting your GraphQL server through excessive resource consumption. Ensuring your API interactions are properly regulated will help maintain the stability and performance of your server.

For more information on building with GraphQL, check out GraphQL Apollo: An Introduction with Examples in our docs.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *