> ## Documentation Index
> Fetch the complete documentation index at: https://0-x.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# GraphQL Attacks

GraphQL lets a client ask for exactly the fields it needs through a single endpoint, instead of hitting a different REST route per resource. That flexibility is also what makes it awkward to secure: one endpoint now fronts the entire data graph, authorization has to be checked per-field rather than per-route, and the query language itself gives an attacker primitives (aliases, fragments, introspection) that a REST API never exposes.

> **Schema Discovery**
>
> Find the endpoint, confirm it's GraphQL, then pull the full schema via introspection, or reconstruct it another way if introspection is disabled.

> **Abuse the Query Language**
>
> Batching, aliasing, and injection through resolvers exploit things that are a normal part of GraphQL, not a bug in any one implementation.

***

## Finding the Endpoint

GraphQL servers are usually found at a predictable path, worth a quick bruteforce pass if it's not obvious from the app itself (check requests in Burp/DevTools first, most front-end apps call it directly).

```bash theme={null}
# Common GraphQL endpoint paths
/graphql
/graphiql
/graphql.php
/graphql/console
/api
/api/graphql
/graphql/api
/graphql/graphql
```

Queries are usually sent as a URL parameter on `GET`, or as a JSON body on `POST`, confirm which the target accepts.

```text theme={null}
<url>/graphql?query={}
```

> [!NOTE]
>
> `graphiql` and `graphql/console` are built-in IDEs some frameworks ship for development, if either is reachable in production it's an instant win: full interactive schema browsing and query execution with no further work needed.

***

## Confirming the Service

A universal query every GraphQL implementation understands, if it comes back instead of a 404 or a parse error, you've got a live endpoint.

```graphql theme={null}
{__typename}
```

A positive response looks like:

```json theme={null}
{"data": {"__typename": "Query"}}
```

***

## Introspection

Introspection is a built-in GraphQL feature that lets the schema describe itself, every type, field, and argument, to the caller. It's meant for tooling during development, but plenty of APIs ship to production with it still enabled.

```graphql theme={null}
{__schema{types{name,fields{name}}}}
```

The expanded version also pulls argument names, descriptions, and types, enough to actually construct valid queries and mutations against fields you've never seen documented anywhere.

```graphql wrap theme={null}
{__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
```

> [!TIP]
>
> Running the full introspection query by hand is slow. [InQL](https://github.com/doyensec/inql) (Burp extension) sends it for you and renders the whole schema as a browsable tree, plus generates template queries/mutations for every field it finds.

***

## When Introspection Is Disabled

Disabling introspection in production is a common hardening step, but it doesn't make the schema unknowable, just slower to recover.

> **Field-suggestion information disclosure**
>
> Most GraphQL servers return a helpful "Did you mean?" error when a query references a field that doesn't exist but is close to one that does. Querying a deliberately wrong field name one character at a time and reading these suggestions back lets an attacker reconstruct the schema field-by-field without ever calling `__schema`.
>
> ```graphql theme={null}
> {user{usernam}}
> ```
>
> ```json theme={null}
> {"errors":[{"message":"Cannot query field \"usernam\" on type \"User\". Did you mean \"username\"?"}]}
> ```
>
> [Clairvoyance](https://github.com/nikitastupin/clairvoyance) automates this exact process, reconstructing 60-80% of a schema this way is typical even with introspection fully disabled and no error-suggestion field left un-patched.

> **Known-schema wordlists**
>
> If field-suggestion errors are also disabled, falling back to a wordlist of common GraphQL type/field names (user, email, password, isAdmin, role, id...) brute-forced against the endpoint still recovers a usable partial schema, slower and noisier, but effective against a fully locked-down target.

***

## Batching and Alias Abuse

GraphQL lets a single HTTP request bundle many separate operations, and lets the same field be queried multiple times under different names (aliases) in one request. Both are legitimate performance features, intended to save round-trips, and both turn into a single request doing the work of dozens.

Query batching sends an array of operations instead of one:

```json theme={null}
[
  {"query": "{user(id:1){name}}"},
  {"query": "{user(id:2){name}}"},
  {"query": "{user(id:3){name}}"}
]
```

Aliasing achieves the same multiplication inside a single operation, useful specifically for brute-forcing a login mutation past a rate limiter that only counts HTTP requests, not resolver calls:

```graphql theme={null}
{
  a1: login(username: "admin", password: "password1") { token }
  a2: login(username: "admin", password: "password2") { token }
  a3: login(username: "admin", password: "password3") { token }
}
```

> [!WARNING]
>
> A naive rate limiter counting requests-per-second sees exactly one request either way, hundreds of login attempts (or database reads) can be smuggled through in a single HTTP call. [BatchQL](https://github.com/assetnote/batchql) automates finding and exploiting both patterns against a target endpoint.

***

## Denial of Service via Query Complexity

Because a query can reference its own type recursively through nested relationships (a user has posts, each post has an author, that author has posts...), a deeply nested or circular query can force the server to do exponentially more work than the request size suggests.

```graphql theme={null}
{
  user(id: 1) {
    friends {
      friends {
        friends {
          friends {
            name
          }
        }
      }
    }
  }
}
```

***

## Injection Through Resolvers

GraphQL is just a query layer in front of whatever datastore the backend actually uses, if a resolver passes an argument straight into a SQL query, a NoSQL filter, or a shell command without parameterizing it, the underlying injection class applies exactly as it would through a REST parameter. The abstraction layer just makes it easier to miss during a code review.

```graphql theme={null}
{
  user(id: "1' OR '1'='1") {
    name
    email
  }
}
```

```graphql theme={null}
{
  user(filter: "{\"$gt\": \"\"}") {
    name
  }
}
```

See [Command Injection](https://0-x.dev/command-injection.md) and the [SQL injection](https://0-x.dev/sql-injection.md) page for the actual exploitation techniques once a resolver argument is confirmed to be inserted into a query unsanitized, GraphQL only changes where the payload is delivered from.

***

## Authorization Bypass (BOLA / BFLA / Mass Assignment)

> **Broken Object-Level Authorization (BOLA/IDOR)**
>
> The classic IDOR, applied to GraphQL: a query accepts an object ID and returns that object's data without checking whether the caller actually owns it. Exactly the same vulnerability as REST IDOR (see [IDOR](https://0-x.dev/insecure-direct-object-reference.md)), just reached through a `query` field instead of a URL query.
>
> ```graphql theme={null}
> {invoice(id: 4812){ amount, customerEmail, pdfUrl }}
> ```

> **Broken Function-Level Authorization (BFLA)**
>
> A mutation meant for admins (`deleteUser`, `promoteToAdmin`) that the schema exposes to every authenticated caller, with authorization only enforced in the UI, not the resolver itself. Worth testing every mutation introspection reveals against a low-privilege session, not just the ones the app's own UI calls.

> **Mass assignment via input objects**
>
> A mutation's input type accepts more fields than the client UI ever sends, if `role` or `isAdmin` is a field on the `UserInput` type, nothing stops a raw request from setting it directly even though the real form never exposes that control.
>
> ```graphql theme={null}
> mutation {
>   updateProfile(input: { name: "test", role: "ADMIN" }) { id, role }
> }
> ```

***

## Quick Reference Tools

> **[HackTricks: GraphQL](https://hacktricks.wiki/network-services-pentesting/pentesting-web/graphql.html)**
>
> Reference used for this page.

> **[InQL](https://github.com/doyensec/inql)**
>
> Burp extension for schema dumping, attack-graph rendering, and query generation.

> **[Clairvoyance](https://github.com/nikitastupin/clairvoyance)**
>
> Reconstructs a GraphQL schema from field-suggestion errors when introspection is disabled.

> **[BatchQL](https://github.com/assetnote/batchql)**
>
> Automates batching and alias-abuse attacks, including DoS and brute-force testing.

> **[graphql-cop](https://github.com/dolevf/graphql-cop)**
>
> Automated baseline security checks against a GraphQL endpoint (introspection, batching, CSRF, field suggestions).

> **[GraphQL Voyager](https://github.com/graphql-kit/graphql-voyager)**
>
> Interactive visualization of a dumped schema, useful for spotting unexpected relationships fast.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.