Exploiting GraphQL: A Full-Spectrum Security Assessment Covering Introspection, Injection, and Resolver Weaknesses
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.
The scan revealed two accessible endpoints:
/graphql
/graphiqlThe /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.
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.
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.
To analyze the results:
- Navigate to the Target tab.
- Open the Site Map view.
- Select the host under assessment.
- Expand the
/graphqlentry.
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:disabledThis 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:disabledTo:
env=graphiql:enabledAfter forwarding the modified request, access to the GraphiQL interface was granted.
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:
- Open browser Developer Tools.
- Navigate to Application → Cookies.
- Locate the relevant cookie.
- Modify the
envvalue tographiql:enabled.
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:
systemUpdateRepeated 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":{}}
]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
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
}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:
OwnerPaste
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
}
}
}
}
}
}
}
}
}
}
}
}
}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
contentfield:
<script>alert(1)</>- In the
titlefield:
<script>alert(2)</script>Upon sending the request and navigating to the page where it was rendered, both scripts executed successfully, resulting in two alert dialogs.
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.
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.txtThis request file was supplied to sqlmap for automated testing.
Confirming the Injection Point
sqlmap -r request.txt --dbms=sqliteThis 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 -tablesThis step enumerated all available tables within the database, confirming database-level access.
Dumping Table Contents
sqlmap -r request.txt --dbms=sqlite -dumpUsing this command, the contents of all tables were extracted. Among the retrieved data were credentials for:
adminoperator
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.
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.
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
After sending the request, the netcat listener immediately received a connection attempt.
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/; idThis 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.
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.
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>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.htmlThe 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.
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.htmlThis 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"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 allThis configuration performs the following:
- Sends POST requests to the
/graphqlendpoint. - Targets the
loginmutation. - 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.
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}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.