Share on
·

GraphQL Injection Attacks

Shahar Binyamin·

Overview

Injection attacks are a very common type of web attack, where an attacker sends malicious input to an API that has an interpreter or parser that processes the input and passes it on to a backend system or database. If the interpreter/parser doesn’t validate or sanitize the input, then the attacker can gain access to or manipulate confidential data.

Why do injection attacks occur in GraphQL?

GraphQL is uniquely vulnerable to injection attacks for a few reasons:

  1. GraphQL introduces new surfaces for injections, through features like operation names and aliases.
  2. GraphQL introduces new steps in the processing flow. Each of the parser, the gateway, and the sub-graph resolvers can be targets for an attack.
  3. GraphQL turns what would be multiple API calls with a REST API into a single call. This single call can target multiple areas and multiply injection combinations because of GraphQL’s free-form nature.

How GraphQL injection attacks work

An injection attack can happen at any phase during the execution of a query:

  1. Parsing: The GraphQL query string is parsed into an abstract syntax tree (AST), a tree-like representation of the query. This step verifies that the query is syntactically correct and adheres to the GraphQL query language specifications.
  2. Validation: The AST is then validated against the GraphQL schema to check that the query is semantically correct and that it is requesting only valid fields and types. This step checks if the query adheres to the GraphQL schema and rules. Since the specification specifies no rules besides uniqueness on operation, argument, alias and directive names, these, if not validated, can be an entry point for an attacker.
  3. Execution: The validated query is then executed by the server using resolvers. For each field requested, there is a resolver function that runs in the backend and fetches the data from the database. The resolver can also serve as an entry point for an attacker. For instance, the following is an example of a GraphQL resolver written using GraphQL.js. It executes a query that returns a customer that matches the supplied id.
Query: {
  customer(obj, args, context, info) {
  const stmt = `SELECT * FROM customers where id = ${id};`;
  const customerData = db.query(stmt)
  return customerData
  }
}

If a value like id: "1' OR 1=1–" is provided, it will result in a SQL injection and the resolver will return all customers, triggering a data breach.

Examples of Injection Attacks

GraphQL injection attacks can lead to a few different scenarios:

  1. DoS (overloading the parser or resolver to a point they crash)
  2. Extract data
  3. Manipulate data

Below, we’ll cover some specific tactics that attackers might use in an injection attack, including Aliases, SQL injections, command injections, XSS assaults, LDAP and NoSQL injections.

SQL injection

A SQL injection involves exploiting systems that use SQL to communicate with their databases. An attacker can inject SQL statements that manipulate a GraphQL query to access, modify or delete data.

For example, an attacker could include the following code in a GraphQL query that retrieves a particular customer's details:

query {
  customer(id: "22371' OR 1=1–") {
    name, 
    email, 
    address, 
    contact
  }
} 

During the execution phase, the above code would cause the query to return all the customer details instead of just the customer matching the original query.

NoSQL injection

A NoSQL injection attack is similar to a SQL injection attack. It targets systems or applications that use NoSQL databases.

The following query is an example of a GraphQL query that accepts a JSON input to search for a user via user names or email addresses.

query {
    users(search: "{\"username\": {\"$regex\": \"jan\"}, \"email\": {\"$regex\": \"jan\"}}",
          options: "{\"skip\": 0, \"limit\": 10}") {
        _id
        username
        fullname
        email
    }
}

The attacker could modify the JSON data type fields to include MongoDB or other NoSQL database-specific commands, which would then be executed by the database when the GraphQL query is processed.

query {
    users(search: "{\"email\": {\"$gte\": \"\"}}",
          options: "{\"fields\": {}}") {
        _id
        username
        fullname
        email
    }
}

During the execution phase, the above query will return all fields specified in the GraphQL schema for all users in the system.

LDAP injection

LDAP is commonly used for storing, retrieving, modifying and searching for data in a hierarchical directory. A GraphQL query that accepts LDAP input could be used to retrieve data from an LDAP directory.

LDAP injection attacks typically target the search filter of an LDAP query, which is used to specify the conditions that must be met for a record to be returned. An attacker can modify the search filter to include malicious code, allowing them to retrieve sensitive information or perform other unauthorized actions. For example, an attacker might modify a search filter to include a wildcard character, which would return all records in the directory.

query {
  user(username: "*") {
    name
    email
    groups
  }
}

During the execution phase, the above query returns details of all users.

Command injection

Command injection is an exploit in which an attacker can inject shell commands or code into a GraphQL query. It allows the attacker to execute arbitrary commands on the server, thus potentially giving them wide access. For example, consider the following GraphQL query

query {
  getUser(id: "1") {
    name
    email
  }
}

An attacker can inject a command into the id field to execute a shell command on the server during the execution phase, such as:

query {
  getUser(id: "1; ls -la") {
    name
    email
  }
}

If the server is using an API that interprets a string as a shell command (for instance is using child_process.execFile), this would cause the server to execute the command ls -la.

XSS (Cross-Site Scripting)

XSS attacks can occur when an attacker injects malicious code into trusted websites, generally in the form of browser scripts. For example, an attacker might inject malicious code into the comment field, such as:

query {
  getComment(id: "1") {
    user
    comment: "<script>alert('XSS Attack')</script>"
  }
}

When a user views the results of this query, the malicious script will be executed in their browser.

Log spoofing

GraphQL actions can take an operation name as part of the query. For example, here is a query that uses CustomName as an operation name:

query CustomName {
    getCustomName
    {
      first
      last
    }
  }

In the audit log, the application will display all queries and mutations that users are executing on the system. If the query had an operation name, it will be recorded in the audit log under that operation name. This creates an opportunity for attackers to “spoof” the log by putting a query under a misleading operation name. For example, the mutation below logs the mutation as getUser when actually, it’s creating a user.

mutation getUser {
 createUser(name:"<script>alert(1)</script>", content:"zzzz", public:true) {
   userId
 }
}

Or, the query below logs arbitrary strings to the audit log:

query arbitraryString {
 systemHealth
}

HTML injection

HTML injection allows an attacker to inject malicious code into a vulnerable web page. HTML injections are similar to XSS attacks. The following query demonstrates how an attacker can create an HTML injection:

mutation {
 createPaste(title:"<h1>hello!</h1><script>alert('Attack')</script>", content:"zzzz", public:true) {
   paste {
     id
   }
 }
}

Prevention and protection against GraphQL injection attacks

Below, we outline some ways to protect your server from GraphQL injection attacks.

Input validation and sanitization

Input validation means having checks in place for GraphQL input. These checks validate that the input has an expected format and doesn’t include special characters frequently used in injection attacks.

For example, in the above SQL injection examples, we see that an attacker supplies input ( 1233' OR 1=1-) that includes the single quote special character.

The dynamic SQL statement in the resolvers might be:

"SELECT * from customers where id='" + input + "'"

After concatenating it with the provided input, the statement is evaluated as:

SELECT * from customers where id='1233' OR 1=1—'

The single quote acts as a string delimiter in SQL. By adding it to the input, the statement searches for customers with an id of 1233 or 1=1. Since 1=1 is always true, it returns all customers. The last single quote is commented out, then, since – and any text after it is considered a comment in SQL.

In this example, input validation would determine that the input contained dangerous special characters (single quote and -); input sanitization would then remove these characters.

While input validation and sanitization can be performed in the resolvers (execution stage), it is recommended to do it earlier, in the validation stage, when the AST is validated against the GraphQL schema to determine that only valid types and fields are being requested. In addition to the basic Int, Float, String, Booleans, types, you can additionally enforce that inputs match custom scalar types, like email addresses. This helps you identify the data and validate it before even transferring it to the server.

Prepared statements

Prepared statements bind the actual data values to the placeholders in the template at runtime. For example, instead of writing a query like this:

SELECT * FROM users WHERE id = '123';

You would use a prepared statement in your resolver like this:

SELECT * FROM users WHERE id = ?;

The ? is a placeholder that temporarily takes the place of the data. The SQL query is pre-compiled with placeholders, and the user’s data is added later. This means that even if a nefarious actor submits a GraphQL query that looks like:

query {
 user(id: "22371' OR 1=1 ") {
    name, 
    email, 
    address, 
    contact
  }
} 

The initial SQL query logic won’t be changed. Instead, the database will look for a user admin whose password is literally “22371' OR 1=1 ”.

Secure libraries, secure implementations

Several well-known languages have libraries and implementations of the GraphQL specification. Attackers take advantage of flaws in these servers or implementations. For example, the use of exec and execSync in @graphql-tools/git-loader before 6.2.6. allows arbitrary command injection. Therefore, it's crucial to pick secure libraries and versions while creating GraphQL APIs. You can learn more about public GraphQL implementations here.

Conclusion

GraphQL injection attacks may undermine critical web applications and systems. To prevent GraphQL injection attacks, it is essential to follow best practices around input validation and the use of secure libraries.

Security tooling often focus on REST APIs input validation and sanitization, leaving GraphQL APIs unaddressed. With Inigo, you can establish rigid guidelines for which operation names (as well as other elements of the document) to accept from clients. This ensures that your policy is rigid regardless of which GraphQL servers you use. The following is an example configuration that validates optional names for a GraphQL API:

kind: Security
name: my_server
spec:
  validation:
    alias_name: "^[a-zA-Z]+$"
    directive_name: "^[a-zA-Z]+$"
    operation_name: "^[a-zA-Z]+$"
    arguments:
      String: "^[a-zA-Z]+$"

Additional Resources

There are many resources available to help developers learn about GraphQL security and injection attacks, including

  1. "GraphQL Cheat Sheet" by OWASP: This guide from OWASP covers the most common security risks associated with GraphQL and provides best practices for mitigating them.
  2. Damn Vulnerable GraphQL Application” - Damn Vulnerable GraphQL Application is an intentionally vulnerable implementation of GraphQL to learn and practice GraphQL Security.
  3. The complete GraphQL Security Guide” - This blog post discusses fixes to the 13 most common GraphQL vulnerabilities to make your API production ready.

Protecting your GraphQL APIs is critical, and this process may be aided with GraphQL security platforms like as Inigo, which operate with any GraphQL server. Inigo has schema-based access control, detailed analytics, dynamic rate-limiting, an easy UI, and much more to help you accelerate API adoption.

Ready to accelerate your GraphQL adoption?
Start Inigo for free
*No credit card needed
Join our newsletter