Sitemap

Exploiting GraphQL: A Full-Spectrum Security Assessment Covering Introspection, Injection, and Resolver Weaknesses

16 min readMar 2, 2026

--

Author: https://kizerh.github.io

Overview

This project involved performing a structured security assessment against the Damn Vulnerable GraphQL Application, an intentionally insecure environment designed to expose common GraphQL misconfigurations and backend vulnerabilities. The objective was to identify, exploit, and analyze weaknesses specific to GraphQL implementations, ranging from schema exposure and authorization failures to injection and denial-of-service conditions.

GraphQL introduces a unique attack surface compared to traditional REST APIs. Instead of multiple endpoints, applications often expose a single /graphql endpoint capable of resolving complex nested queries. When improperly configured, this model can expose sensitive schema data, allow unbounded query execution, or provide attackers with a powerful interface to backend logic.

Tools Used

  • Damn Vulnerable Graphql Application, for information on how to install the application check https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application
  • Burp Suite was used for intercepting and manipulating GraphQL queries and HTTP requests.
  • GraphQL Voyager was used for visualizing the schema structure once introspection was obtained.
  • gql-finder, a custom-built open-source tool, was used for endpoint discovery and initial GraphQL surface mapping. https://github.com/kizerh/gql-finder
  • graphwOOf was used to fingerprint the GraphQL engine and extract implementation details. https://github.com/dolevf/graphw00f
  • sqlmap was used where backend injection opportunities were suspected.

Execution

Section 1: Reconnaissance

The first phase of the assessment focused on identifying exposed GraphQL endpoints and fingerprinting the underlying implementation. Because GraphQL applications typically expose a limited number of endpoints, accurate discovery at this stage is critical. A single overlooked endpoint can provide full schema visibility and direct access to backend logic.

1.1 Endpoint Discovery Using gql-finder

To identify potential GraphQL endpoints, I used my custom-built open-source tool, gql-finder. The tool is designed to enumerate common GraphQL paths and detect GraphQL behavior through response analysis.

Press enter or click to view image in full size

The scan revealed two accessible endpoints:

/graphql
/graphiql

The /graphql endpoint is the primary API interface used for query and mutation execution.

The /graphiql endpoint exposed the interactive GraphiQL developer interface. This interface, when accessible in production environments, significantly lowers the barrier for attackers by allowing interactive schema exploration and query testing directly from the browser.

1.2 Engine Fingerprinting Using graphwOOf

After endpoint discovery, the next step was to fingerprint the GraphQL engine and gather additional metadata. For this, I used graphwOOf, a reconnaissance tool designed specifically for GraphQL services.

Press enter or click to view image in full size

The tool was used to:

  • Detect the graphQL engine used (Graphene)
  • The attack surface matric, the technologies used ad the default settings that comes with the engine

Section 2: Schema Enumeration and Interface Bypass

With endpoint reconnaissance completed, the next phase focused on extracting the full GraphQL schema and analyzing the available attack surface.

2.1 Extracting the Full Schema via Introspection

Using Burp Suite’s built-in GraphQL functionality, an introspection query was sent to the /graphql endpoint. Burp automatically generates a complete introspection query that retrieves: All available types, Queries and mutations, Input objects, Arguments, and Field relationships.

Press enter or click to view image in full size
click on any feature on the app that sends a request to the /graphql endpoint, send the request to repeater, right click on the requester, click “graphQL” and then “send introspection query”

After receiving the response, the schema output was saved into Burp’s Site Map. This step is important operationally because it allows structured navigation of the API surface without repeatedly sending introspection queries.

Press enter or click to view image in full size

To analyze the results:

  1. Navigate to the Target tab.
  2. Open the Site Map view.
  3. Select the host under assessment.
  4. Expand the /graphql entry.
Press enter or click to view image in full size

Burp automatically parses and presents the introspection response, allowing visibility into all discovered queries and mutations.

2.2 Attempting to Access the GraphiQL Interface

After schema extraction, attention shifted to the /graphiql endpoint. Direct access initially returned an access denied response.

I inspected the request and response flow in Burp Suite. During analysis of the request headers and cookies, I identified a variable:

env=graphiql:disabled
Press enter or click to view image in full size

This suggested that access control for the GraphiQL interface was controlled client-side via a cookie value rather than being enforced server-side.

2.3 Cookie Manipulation to Bypass Interface Restriction

To test this hypothesis, I intercepted the request in Burp and modified the cookie value:

From:

env=graphiql:disabled

To:

env=graphiql:enabled

After forwarding the modified request, access to the GraphiQL interface was granted.

Press enter or click to view image in full size

This confirmed that the restriction mechanism relied on client-controlled state rather than robust server-side validation. Such an implementation can be bypassed trivially by any user capable of modifying cookies.

To make the change persistent in the browser session:

  1. Open browser Developer Tools.
  2. Navigate to ApplicationCookies.
  3. Locate the relevant cookie.
  4. Modify the env value to graphiql:enabled.
Press enter or click to view image in full size

After refreshing the page, the interactive GraphiQL interface remained accessible.

Section 3: Denial of Service Exploitation

After completing schema enumeration, I began reviewing available queries for performance characteristics and potential abuse cases. During this review, one resolver stood out:

systemUpdate
Press enter or click to view image in full size

Repeated execution showed that this query required significantly more processing time than other queries (note the processing time at the bottom right corner). Any resolver that is computationally expensive becomes a prime candidate for denial-of-service testing, particularly in GraphQL where multiple queries can be combined within a single request.

This phase focused on abusing GraphQL query flexibility to amplify server workload without triggering obvious rate-based defenses.

3.1 Batch Query Attack

GraphQL supports batching, allowing multiple queries to be submitted within a single HTTP request. If the backend does not enforce batch limits or query cost restrictions, attackers can force the server to execute expensive resolvers repeatedly in parallel.

To test this, I submitted the following request body:

[
{"query":"query {\n systemUpdate\n}","variables":{}},
{"query":"query {\n systemUpdate\n}","variables":{}}
]
Press enter or click to view image in full size

This forces the server to process the systemUpdate resolver twice in one request (note the time). By increasing the number of entries in the array, the workload scales linearly or worse depending on backend implementation. When more than two queries wee batched it completely crashed the server

Press enter or click to view image in full size

Because batching occurs within a single HTTP transaction, traditional rate limiting based on request count or WAF may not detect the amplification. This makes batch abuse particularly dangerous when combined with expensive resolvers.

3.2 Alias Abuse

GraphQL aliases allow the same query to be executed multiple times within a single operation under different names. When query complexity is not restricted, aliases can replicate heavy operations without using batch arrays.

The following payload was used:

query {
u1: systemUpdate
u2: systemUpdate
}
Press enter or click to view image in full size

Although syntactically clean, this forces the resolver to execute multiple times during a single request lifecycle.

Likewise sending more than two aliases completely crashed the server

3.3 Deep Recursion Query Attack

Using the introspection output imported into GraphQL Voyager, I visualized object relationships within the schema. This revealed a circular reference between two types:

  • Owner
  • Paste
Press enter or click to view image in full size
Press enter or click to view image in full size

Each owner contains pastes, and each paste references an owner. This bidirectional relationship enables recursive nesting.

Leveraging this structure, I crafted a deeply nested query:

query {
pastes {
owner {
pastes {
owner {
pastes {
owner {
pastes {
owner {
pastes {
owner {
pastes {
owner {
name
}
}
}
}
}
}
}
}
}
}
}
}
}
Press enter or click to view image in full size
normal request response time (1,000ms)
Press enter or click to view image in full size
Press enter or click to view image in full size

This query forces repeated traversal of circular object relationships, dramatically increasing resolver execution cost.

In GraphQL implementations without depth limiting or query complexity analysis, such recursive queries can; Consume excessive CPU resources, Increase memory usage, Trigger long response times, Cause backend thread exhaustion

Because this exploitation occurs at the application layer rather than network flooding, it bypasses traditional volumetric DDoS protections.

Section 4: Injection Attacks

After evaluating availability weaknesses through denial-of-service testing, attention shifted to input handling within resolvers. GraphQL does not inherently protect against injection vulnerabilities. If resolver logic forwards user-controlled input directly into database queries or rendering templates, traditional injection classes remain exploitable.

4.1 Stored Cross-Site Scripting

While reviewing mutations discovered during schema enumeration, the createPaste mutation was identified as a potential injection point. The mutation accepts user-controlled input for both title and content fields, which are later rendered in the application interface.

To test for stored XSS, the following payloads were injected simultaneously:

  • In the content field:
<script>alert(1)</>
  • In the title field:
<script>alert(2)</script>
Press enter or click to view image in full size

Upon sending the request and navigating to the page where it was rendered, both scripts executed successfully, resulting in two alert dialogs.

Press enter or click to view image in full size
Press enter or click to view image in full size

This confirmed that:

  • Input was stored without sanitization.
  • Output was rendered without proper encoding.
  • Script execution occurred in the browser context.

Because the payload was stored server-side and executed whenever the paste was viewed, this constitutes a stored XSS vulnerability. In a real-world application, such a flaw could enable:

  • Session hijacking
  • Credential theft
  • CSRF token exfiltration
  • Privilege escalation through administrative account compromise

4.2 SQL Injection

During manual probing of query arguments, a parameter named filter of the pastesquery was identified as potentially vulnerable. When a single quote was appended to the argument value, the server returned an SQLite error message.

Press enter or click to view image in full size

This behavior indicated that user input was likely being interpolated directly into a SQL statement without proper parameterization.

To confirm exploitation potential, the value was first modified to "*" to prepare it for sqlmap automatic testing. The full HTTP request containing the vulnerable parameter was then saved locally as:

request.txt
Press enter or click to view image in full size

This request file was supplied to sqlmap for automated testing.

Confirming the Injection Point

sqlmap -r request.txt --dbms=sqlite
Press enter or click to view image in full size

This command instructed sqlmap to:

  • Replay the exact HTTP request
  • Target SQLite specifically
  • Detect and confirm injection points

The tool confirmed that the filter parameter was injectable.

Enumerating Database Tables

sqlmap -r request.txt --dbms=sqlite -tables

This step enumerated all available tables within the database, confirming database-level access.

Press enter or click to view image in full size

Dumping Table Contents

sqlmap -r request.txt --dbms=sqlite -dump

Using this command, the contents of all tables were extracted. Among the retrieved data were credentials for:

  • admin
  • operator
Press enter or click to view image in full size

The successful extraction of privileged account credentials demonstrates full database compromise via SQL injection.

Section 5: Server-Side Request Forgery

During resolver enumeration, a mutation named importPaste was identified. Its purpose was to retrieve paste content from a user-supplied external source. Any feature that allows the backend to fetch remote resources is immediately high-risk, as it can potentially be abused to perform Server-Side Request Forgery.

The presence of this mutation indicated that the application would make outbound HTTP requests based on user-controlled input.

Press enter or click to view image in full size

5.1 Initial SSRF Testing

To test whether the mutation could be abused to access internal services, I supplied a localhost address as the external source parameter. Instead of returning meaningful output, the application appeared to hang and eventually returned an empty response.

Press enter or click to view image in full size

This behavior suggested that:

  • The backend was attempting to reach the supplied address.
  • The request may have been blocked or unreachable.
  • The application did not return meaningful error handling output.

To confirm whether the backend was actually attempting the internal connection, I needed a reliable way to observe outbound traffic from within the container running the application.

5.2 Setting Up an Internal Listener

Because the target application was running inside a Docker container, I opened a listening port inside that same container to detect internal requests.

First, I started a listener:

docker exec -it dvga sh -lc 'nc -lvnp 1234'

If nc is not installed, you need to install it inside the container using root privileges:

docker exec -it -u 0 dvga bash -lc "apt-get update && apt-get install -y netcat-openbsd"

After installing netcat, start the listener:

docker exec -it dvga sh -lc 'nc -lvnp 1234'

This opened a listening socket on port 1234 inside the container.

5.3 Confirming SSRF Behavior

With the listener active, i sent the importPaste request with the localhost and the port again

Press enter or click to view image in full size

After sending the request, the netcat listener immediately received a connection attempt.

Press enter or click to view image in full size

This confirmed that:

  • The backend server was making HTTP requests based on user input.
  • The request was executed within the container context.
  • The mutation could access internal services.

Even though the application returned an empty response in the UI, the backend network activity proved that SSRF was successful.

This vulnerability allows attackers to:

  • Access internal services running on localhost
  • Interact with services not exposed externally
  • Potentially reach metadata services in cloud deployments
  • Pivot to internal network scanning
  • Interact with internal management ports

In real-world environments, SSRF vulnerabilities can be leveraged to extract cloud instance metadata, access internal administrative panels, or pivot deeper into the infrastructure.

Section 6: Command Injection

After confirming SSRF through the importPaste mutation, I began analyzing whether the backend was invoking system-level commands to fetch external resources. Applications that use utilities such as curl, wget, or similar shell-based mechanisms to retrieve remote content often introduce command injection risk when user input is not properly sanitized.

6.1 Command Injection via importPaste Mutation

Since the importPaste mutation accepted a user-controlled external source, and SSRF was already confirmed, the next step was to test whether the application was constructing a system command internally.

To test for shell injection, I appended a command separator to the input:

"path": "/; id"

The semicolon is commonly used in shell environments to chain commands. If the application internally executed something like:

curl http://kizerh.github.io/

Then appending ; id would result in:

curl http://kizerh.github.io/; id

This would execute the id command on the server after the initial command.

Upon sending the modified mutation request, the response confirmed that the injected command executed successfully. The output of the id command was returned, demonstrating that arbitrary system commands could be executed.

Press enter or click to view image in full size

This confirmed that:

  • The backend was invoking a shell command using unsanitized user input.
  • No input validation or command isolation was implemented.
  • The mutation was vulnerable to OS-level command injection.

The impact of this vulnerability is severe, as it allows remote code execution under the privileges of the application process.

6.2 Command Injection via systemDiagnostic Query

During schema enumeration, another resolver named systemDiagnostic was identified. This query accepted a direct argument “cmd” indicating the command to run. However, unlike the previous mutation, this resolver required authentication. However, earlier SQL injection exploitation had exposed credentials for privileged accounts, including the admin account.

Using the extracted admin credentials, I authenticated successfully and accessed the protected query.

Press enter or click to view image in full size

This second injection path differed from the first in that it was explicitly designed to execute system commands. However, access control was insufficient because:

  • The resolver exposed direct command execution functionality.
  • Authentication alone was relied upon for protection.
  • No command restrictions or whitelisting were enforced.

Once administrative credentials were compromised through SQL injection, this resolver effectively provided direct remote code execution capability.

Section 7: Path Traversal and Arbitrary File Write

Following command injection validation, I continued reviewing resolvers that interact with the file system. A mutation named uploadPaste was identified, which allows users to upload content from their local system and store it server-side.

Any resolver that writes files to disk introduces potential risks, especially if user-controlled file paths are accepted without normalization or restriction.

7.1 Initial File Upload Behavior

To observe baseline behavior, I uploaded a simple file named index.html containing:

<h1>hello</h1>
Press enter or click to view image in full size
Press enter or click to view image in full size

The file was successfully uploaded and rendered in the application interface as an H1 header. This confirmed two important behaviors:

  • Uploaded content was stored and retrievable.
  • HTML content was rendered directly without sanitization.

While the primary focus here was file handling, this behavior also indicates weak output encoding practices (HTML Injection).

7.2 Attempting Directory Traversal

After confirming upload functionality, I tested whether the resolver properly restricted file paths.

Instead of using a standard filename, I supplied a traversal sequence:

../../../../../../etc/path_traversal.html
Press enter or click to view image in full size

The application returned a permission denied error. Although the write failed, this response was significant because it confirmed that the application attempted to resolve the supplied path literally. Properly secured file handling should normalize paths and restrict writes to a designated directory before filesystem interaction occurs.

Additional attempts targeting /usr produced similar permission errors.

Press enter or click to view image in full size

This behavior confirmed that:

  • The application did not sanitize traversal sequences.
  • Path resolution occurred before validation.
  • The backend attempted access to sensitive system directories.

7.3 Successful Arbitrary File Write to /tmp

Since writes to system directories failed due to permission constraints rather than validation controls, I tested a less restricted directory:

../../../../../tmp/path_traversal.html
Press enter or click to view image in full size

This time, the application returned a normal success response.

To confirm whether the file was actually written to disk, I leveraged the previously identified command injection vulnerability. Using system command execution, I inspected the target path:

"/; cat /tmp/path_traversal.html"
Press enter or click to view image in full size

The contents of the uploaded file were present in the /tmp directory, confirming that arbitrary file write was successful.

This vulnerability allows attackers to:

  • Write files outside intended upload directories
  • Potentially overwrite application resources
  • Stage malicious files for later execution
  • Drop backdoors or persistence mechanisms
  • Combine with command injection for privilege escalation

The fact that writes to /etc and /usr failed due to permission restrictions does not mitigate the vulnerability. It demonstrates that exploitation impact is determined by runtime privileges rather than secure input validation.

Section 8: Brute Force and Authentication Weaknesses

After exploiting database-level vulnerabilities and extracting valid credentials, the next logical step was to evaluate the robustness of the authentication mechanism itself. Even when credentials are unknown, weak login rate limiting can allow attackers to brute force accounts directly through the GraphQL API.

The login mutation was identified as the authentication entry point. Because GraphQL exposes authentication through structured mutations rather than traditional form submissions, brute force testing must be performed at the API level.

8.1 Testing Login Rate Limiting

To assess whether the application enforced any throttling or lockout mechanisms, I attempted to brute force the password for the admin account using ffuf.

The following command was used:

ffuf -u http://localhost:5013/graphql \
-X POST \
-H "Content-Type: application/json" \
-d '{"query":"mutation($password: String, $username: String){ login(password: $password, username: $username){ accessToken refreshToken }}","variables":{"password":"FUZZ","username":"admin"}}' \
-w password.txt \
-t 20 \
-rate 50 \
-mc all

This configuration performs the following:

  • Sends POST requests to the /graphql endpoint.
  • Targets the login mutation.
  • Iterates through a password wordlist.
  • Uses 20 concurrent threads.
  • Sends up to 50 requests per second.
  • Matches all HTTP response codes to observe behavioral differences.
Press enter or click to view image in full size
Press enter or click to view image in full size
Press enter or click to view image in full size

The attack traffic was successfully processed without any blocking, throttling, or delay mechanisms being triggered. No rate limiting, CAPTCHA enforcement, or account lockout controls were observed.

Section 9: Information Disclosure via Stack Trace Errors

During the assessment, I also tested how the application handled malformed GraphQL requests and whether it exposed internal debugging information. Error handling is often overlooked in GraphQL deployments, especially when development settings leak into environments that should behave like production.

To trigger an error deliberately, I sent an invalid query through the GraphiQL interface:

query {kiza}
Press enter or click to view image in full size

Because kiza is not a valid field on the root Query type, the server correctly returned a GraphQL validation error. However, the response also included detailed stack trace information. The returned error output exposed internal implementation details such as:

  • The application framework and runtime components involved in request handling
  • Absolute filesystem paths within the server environment
  • Library and package names and their locations
  • Function call flow and execution context from the application stack

Conclusion

The assessment demonstrated how a misconfigured GraphQL application can expose a wide range of vulnerabilities when introspection, resolver validation, and query limits are not properly enforced.

  • Full schema exposure via introspection
  • Unauthorized access to developer tooling interfaces
  • Application-layer denial of service through query complexity abuse
  • Backend command injection and SSRF
  • Persistent client-side injection through stored XSS
  • Arbitrary file write via path traversal
  • Information disclosure through stack traces
  • No rate limiting controls susceptible to brute force

This project highlights the importance of treating GraphQL as a high-privilege interface that requires strict controls such as introspection disabling in production, query depth limiting, input sanitization, resolver-level authorization checks, and robust error handling.

The assessment reinforces that GraphQL security requires both API-layer protections and secure backend development practices. When those controls are absent, a single exposed endpoint can become a powerful attack surface.

--

--