
GraphQL exploitation presents a sophisticated challenge for security engineers, extending far beyond simple introspection queries. A particularly potent and often overlooked vulnerability class is the secondary context attack, which weaponizes the very architecture that makes GraphQL efficient. In many enterprise applications, GraphQL servers act as a Backend-for-Frontend (BFF), receiving a client request and then making a second, trusted request to an internal microservice or REST API. This internal hop is where the danger lies. If an attacker can control a parameter in the initial request—such as a field with a weak ID or String scalar type—they can manipulate the second request through techniques like path traversal, bypassing security controls and accessing sensitive internal endpoints. This guide, based on an expert talk by Willis Vandevanter, breaks down this attack pattern from first principles. You will learn a practical methodology for discovering and chaining these vulnerabilities for maximum impact, and master the defensive strategies needed to build resilient, secure-by-default GraphQL APIs.
Key Takeaways
- You'll learn how to identify and exploit secondary context attacks in GraphQL APIs by manipulating `ID` and `String` scalar types to achieve path traversal.
- You'll be able to build a testing methodology to systematically uncover hidden backend REST API endpoints and vulnerabilities like SQL injection by chaining path traversal with information disclosure.
- Apply a secure-by-default approach to defending GraphQL APIs by implementing custom, type-safe scalers and avoiding hard-coded authorization at the proxy layer.
Understanding Secondary Context Attacks in BFF Architectures
Most modern GraphQL exploitation techniques leverage underlying architectural patterns, and one of the most critical to understand is the secondary context attack. This attack is particularly potent in applications that use a Backend-for-Frontend (BFF) design pattern, which is a common architectural choice for enterprise-level systems.
The Backend-for-Frontend (BFF) Pattern Explained
The BFF pattern involves an intermediate server that stands between the client-side front end and a collection of backend microservices or REST APIs. The core function of the BFF is to aggregate data and handle logic specific to a particular front-end experience (e.g., a web app vs. a mobile app).
The critical security implication of this pattern is that it creates a two-step request flow:
- Primary Context (Untrusted Zone): The user’s client makes an initial request to the BFF. This request originates from an untrusted environment.
- Secondary Context (Trusted Zone): The BFF processes the initial request and then makes a second, new request to an internal microservice or REST API. This second request originates from within the application’s trusted internal network.
For example, a client request to company.com/api/users/{userID} might cause the BFF to make an internal request to user.microservice/users/{userID}. The parameter from the first context is passed directly into the construction of the second.
How a Secondary Context Attack Works
A secondary context attack occurs when an attacker manipulates a controllable parameter in the primary request to maliciously alter the destination or behavior of the secondary request. The BFF is tricked into making a harmful request on the attacker’s behalf from within the trusted zone.
A common vector for this is path traversal. Consider this flow:
- An attacker identifies a controllable parameter, such as an
id, in the initial API call. - Instead of a legitimate ID, they submit a path traversal payload:
../../account/12919. - The BFF, unaware of the malicious intent, takes this input and appends it to the internal API path, constructing a new URL like:
user.microservice/users/../../account/12919. - When the server normalizes this path, the
..characters navigate up the directory tree, and the final request targets a completely different endpoint:user.microservice/account/12919.
The attacker has successfully abused the BFF’s trust in user input to access a different “context”—the account endpoint instead of the intended user endpoint—potentially leading to vulnerabilities like Insecure Direct Object References (IDORs).
The Critical Factor: Relaxed Internal Authorization
This attack becomes especially powerful when authorization is relaxed or removed between the BFF and the internal microservices. Developers sometimes hard-code a high-privilege authorization token (like a JWT or static API key) at the BFF layer, assuming all internal communications are secure.
When an attacker performs a secondary context attack via path traversal, their malicious request to a different microservice inherits this hard-coded, high-privilege token. This can grant them access to highly sensitive services, such as payment or order APIs, that they could never have reached directly. This anti-pattern often arises when different development teams manage various microservices, each with its own threat model, leading to insecure blanket permissions being set at the BFF level.
Actionable Takeaways
- During threat modeling and architecture review, scrutinize the trust boundary between the BFF and backend microservices. Specifically question how authorization is handled for these internal "server-to-server" requests and challenge any use of hard-coded, static credentials.
- When testing an application, if you identify a user-controlled parameter that is reflected in the path of a subsequent internal request (often visible in error messages), prioritize testing for path traversal payloads (`../../`) to determine if you can control the internal API endpoint being called.
- Identify potential BFF patterns by observing application traffic. If a request to a public-facing domain results in verbose error messages that disclose internal service names (e.g., `user.microservice` or `apiv1.context`), it is a strong indicator of a two-step request flow vulnerable to secondary context attacks.
Common Pitfalls
- Hard-coding a single, high-privilege authorization token (e.g., JWT, API Key) at the BFF layer for all subsequent requests to internal microservices. An attacker who can control the request path inherits this token and its permissions.
- Implicitly trusting user-supplied input (especially IDs and strings) when constructing the path or parameters for a secondary, internal API call, which directly enables path traversal.
Why GraphQL APIs are Prime Targets for Path Traversal
Effective GraphQL exploitation often begins by understanding and abusing the API’s underlying architecture. GraphQL servers are frequently implemented using the Backend-for-Frontend (BFF) design pattern, where the GraphQL server receives a client-side query and, in turn, makes a second, trusted request to an internal REST API or microservice. This two-hop process creates the perfect entry point for secondary context attacks. The initial GraphQL query represents the primary context, which an attacker can control, and the subsequent internal API call is the secondary context, which can be manipulated.
When the GraphQL server improperly handles input from the primary context, it opens the door for an attacker to influence or control the request made in the secondary context. If an attacker can inject a path traversal payload into a GraphQL input field, they can trick the BFF into targeting a completely different and potentially unauthorized internal endpoint, leading to a classic GraphQL path traversal vulnerability.
The Root Cause: Insecure GraphQL Scalar Types
A common misconception is that GraphQL’s strong typing system inherently prevents such injection attacks. However, the root of this vulnerability lies within two of GraphQL’s five default scalar types: String and ID.
-
StringScalar: This type accepts any valid string, providing no built-in sanitization. As a result, a field expecting a simple string will also accept a path traversal payload like../api/v1/accountsas valid input. -
IDScalar: This is the most critical and often overlooked vector. According to the official GraphQL documentation, theIDtype “serializes the same as string” and merely signifies that the value is “not intended to be human-readable.” It does not enforce a specific format like a UUID or integer. Therefore, an input field of typeIDis functionally identical to aStringand will readily accept a path traversal payload.
This behavior is a critical weakness. When a developer uses a default ID scaler for a field like accountId, they may implicitly trust that GraphQL will only permit valid identifiers. In reality, the server will accept a malicious string, append it to the internal REST API path, and inadvertently allow the attacker to control the destination of the trusted, secondary request. This makes any query or mutation using these insecure GraphQL scalar types a high-priority target for security testing.
Actionable Takeaways
- Systematically identify and test all GraphQL queries and mutations that use the default `ID` or `String` scalar types as input. Treat any `ID` field as a potential vector for path traversal and other injection attacks, not just as a standard identifier.
- When you identify a potential injection point, confirm path control by sending simple traversal payloads (e.g., `../test`) and observing error messages or changes in the backend response structure. This verifies your ability to manipulate the request path in the secondary context before attempting a full exploit.
Common Pitfalls
- Assuming the `ID` scalar type enforces a specific format like a UUID or integer. This type behaves identically to a `String` and will accept arbitrary text, including path traversal payloads, making it a common blind spot for both developers and testers.
- Relying solely on GraphQL's type system for input validation. The default `String` and `ID` types are inherently unsafe and require additional, explicit server-side validation logic to sanitize input and prevent injection attacks.
A Methodology for Discovering and Exploiting GraphQL Vulnerabilities
A systematic approach is crucial for effective GraphQL exploitation, especially when targeting complex vulnerabilities like secondary context attacks. This methodology provides a repeatable framework for security engineers to discover and chain vulnerabilities, moving from initial fingerprinting to full exploitation. The core of this process involves identifying insecure input handling, confirming control over backend API calls, and leveraging that control to pivot to other internal endpoints.
Phase 1: Fingerprinting and Initial Reconnaissance
The first step is to identify a GraphQL endpoint and gather as much information as possible about its schema.
- Fingerprinting: A reliable method for discovering GraphQL servers, even across a large set of hosts, is to send a
POSTrequest with a malformed query. Many servers respond with verbose validation errors that uniquely identify them as GraphQL endpoints, which can be more effective than sending standard introspection queries that might be blocked. - Schema Discovery:
- Introspection: If enabled, this is the most straightforward way to get the full schema.
- Client-Side Review: In Single Page Applications (SPAs), it’s common for queries, mutations, and schema fragments to be hardcoded in client-side JavaScript. Tools like Burp Suite’s BChecks can be used to statically analyze JS files and reconstruct parts of the schema.
- Suggestions: Some servers provide “Did you mean…?” suggestions for guessed object names. A tool like Clairvoyance automates this process to rebuild the schema when introspection is disabled.
- Brute-Forcing and Error Analysis: If the above methods fail, the last resort is to brute-force common object and field names. The goal is to trigger validation errors that leak information. As seen in the case study, an error might disclose required input types or, even better, the full path of the internal REST API endpoint the resolver is trying to contact (e.g.,
apiv1/context).
Phase 2: Identifying Vulnerable Inputs
The entry point for a GraphQL path traversal attack is almost always an input field that uses one of GraphQL’s weakly-validated default scalar types.
- Focus on Insecure GraphQL Scalar Types: The
IDandStringscalar types are functionally identical and accept any string value, including path traversal payloads (../). They offer no built-in validation. When you see a query or mutation that accepts anIDas input, it should be treated as a major red flag and an immediate target for testing. - Tool-Assisted Discovery: To efficiently find these targets while walking an application, use tooling to highlight requests with these scalar types. A Bambdas script in Burp Suite can create a custom proxy filter or column that flags any request containing
(ID:,(id:, or(string:. This allows you to quickly spot potentially vulnerable operations among the high volume of traffic from modern web apps.
Phase 3: Confirming Path Control
Once a suspicious input is identified, the next step is to confirm that you can actually control the path of the secondary request.
- Fuzz for Reflection: Replace the expected input (e.g., a UUID) with a simple, unexpected string like “not-real-id” or “test”. Look for an error message (often a
404 Not Found) that reflects your input at the end of a URL path (e.g.,microservice/api/v1/accounts/not-real-id). This confirms that your input is being appended to the path of the internal request. - Confirm Path Traversal: Send a classic GraphQL path traversal payload like
../test. If the server responds with an error showing a normalized path (e.g.,microservice/api/v1/test), you have confirmed the ability to break out of the intended directory. - Map the API Root: To understand the boundaries of your attack, recursively add
../segments to your payload until the server response changes, typically to a400 Bad Request. This usually indicates you have reached the root of the file system or web server, defining the scope of your traversal. - Bypass WAFs: Remember to try different URL encodings for the traversal payload (e.g.,
..%2f) to bypass WAFs or other input filters.
Phase 4: Chaining for Exploitation
A confirmed path traversal is not the final exploit; it’s the primitive used to reach other vulnerabilities. The impact comes from chaining it with another piece of information or weakness.
- Chaining with Information Disclosure: In one case study, error-based brute-forcing revealed an internal endpoint (
/api/v1/query) vulnerable to SQL injection but with no direct input. The path traversal vulnerability in a different query (contact) was used to pivot and send a payload to this hidden endpoint, achieving blind SQL injection. - Chaining with IDOR: In another example, a direct IDOR was impossible because the API path was scoped to the current user (
/api/me/accounts/{account_id}). By using path traversal (../../users/{other_user_id}/accounts/{other_user_id}), the attacker broke out of the/me/scope and accessed an endpoint that lacked proper authorization checks, successfully retrieving another user’s account data. This was possible because the authorization token was likely hard-coded at the BFF, granting the request broad internal permissions.
Case Study 1: Chaining Path Traversal and Info Disclosure for Blind SQL Injection
-
Initial Reconnaissance and Information Disclosure: The target was a GraphQL API with introspection disabled. The first step involved sending queries for guessed object names (e.g.,
client) without required sub-fields. This intentionally caused a validation failure, which leaked information about the expected query structure, confirming the existence of valid objects. -
Discovering a Latent SQLi Endpoint via Brute-Forcing: By brute-forcing common object names, an interesting error was triggered for the object
contact. The error message disclosed the full backend REST API call, revealing a highly sensitive internal endpoint:API/V1/query. The error also leaked the parameters for this endpoint, which weretableandcql(a SQL query string). However, the GraphQLcontractquery itself accepted no input, making this SQL injection point unreachable directly. -
Identifying Path Traversal in a Separate Query: Concurrently, another GraphQL query,
contact, was found to accept acontactIDas input. Fuzzing this input with a simple string likenot real IDproduced an error showing the input value was being directly appended to the backend URL path. -
Confirming Path Control: To confirm a GraphQL path traversal vulnerability, the
contactIDwas set to a traversal payload like../../Test. The server’s error response confirmed the backend request was made toAPI/V1/test, proving that the attacker could control the directory and endpoint being called in this secondary context attack. -
Chaining Vulnerabilities for Exploitation: The final exploit chained the two previously discovered vulnerabilities. The
contactquery’s path traversal (Step 4) was used as a carrier to target the latent, uncontrollable SQL injection endpoint found via thecontractquery (Step 2). -
Executing a Blind SQL Injection: The
contactIDinput was crafted with the payload../../API/V1/query?[BLIND_SQLI_PAYLOAD]. The SQL injection had to be blind (e.g., time-based) because the GraphQL server expected to receive acontactobject from the backend. If the backend REST API returned a200 OKresponse with SQL results, the GraphQL layer would see a type mismatch, discard the data, and returnnullto the client. By using timing attacks, the attacker could exfiltrate data from the database despite the mismatched response structure, achieving a critical impact.
Case Study 2: Exploiting a GraphQL Path Traversal for Cross-Account Data Access (IDOR)
This case study demonstrates a chained GraphQL exploitation flow, combining a path traversal vulnerability within an ID scalar type with an Insecure Direct Object Reference (IDOR) to achieve cross-account data access.
- Identify the Target Query: The attacker identifies a GraphQL query,
getAccountById, which uses a GUID as anaccountIDto retrieve the current user’s private account data. The schema likely defines this input using theIDscalar type. - Fuzz Input to Confirm Path Injection: To test for a secondary context attack, the attacker replaces the expected GUID with an unexpected string like
fake_uuid. The server responds with a404 Not Founderror, which importantly discloses the backend REST API path structure:microservice/api/v1/me/accounts/fake_uuid. This confirms the user-controlledaccountIDis being appended to an internal API path. - Verify Path Traversal Vulnerability: The attacker submits a simple GraphQL path traversal payload,
../test, as theaccountID. The backend path normalizes to.../api/v1/me/test, demonstrating that the attacker can successfully escape the intended/accounts/directory. - Chain Path Traversal with IDOR: An attempt to directly access another user’s data by providing their
accountIDfails because the backend path is scoped to the current user via/me/. The final exploit chains the vulnerabilities:- The attacker leaks a second user’s GUID from another part of the application.
- A malicious path traversal payload is crafted for the
accountIDfield. Based on the backend path structure, the payload is constructed to navigate up from the/me/accounts/directory and reconstruct a path to the target user’s data, such as../../users/{VICTIM_GUID}/accounts/.
- Achieve Unauthorized Data Access: The GraphQL server processes the malicious input, makes a request to the manipulated backend path, and successfully retrieves the victim’s account data. This confirms that the GraphQL server, acting as a BFF, was likely using a hardcoded authorization header for internal requests, which the attacker inherited through the path traversal.
Actionable Takeaways
- Prioritize testing of any GraphQL query or mutation that accepts an **`ID`** scalar type as input. Treat it as equivalent to a `String` and immediately test for **GraphQL path traversal** by first confirming input reflection in an error message, then using `../` payloads.
- Leverage tooling to streamline discovery. Implement a **Bambdas** filter in Burp Suite or a similar proxy highlight rule to automatically flag GraphQL requests containing `ID` or `String` inputs, allowing you to focus your manual testing efforts on the most likely vulnerable targets.
- When introspection is disabled, systematically brute-force common GraphQL object and field names to trigger verbose validation errors. These errors can reveal internal REST API endpoints and data structures, providing the necessary intelligence to construct a successful **secondary context attack**.
Common Pitfalls
- Abandoning an IDOR attempt too early. A direct attempt to access another user's resource might fail due to path scoping (e.g., `/api/me/accounts/{other_user_id}`). The mistake is not realizing that a path traversal is required to first break out of the user-specific scope before targeting the other user's resource.
- Giving up if automated schema discovery tools fail. Relying solely on introspection or tools like Clairvoyance is a pitfall. If they fail, an attacker must shift to a manual, error-based enumeration methodology by fuzzing inputs and analyzing verbose error messages to map out the backend.
Key Tools:
- Clairvoyance: An automation tool used to discover a GraphQL schema by guessing objects and interpreting “Did you mean…?” suggestions when introspection is disabled.
- BChecks: Tools used for static analysis of client-side JavaScript to find hardcoded GraphQL queries, mutations, or schema fragments, aiding in schema reconstruction.
- Bambdas: A Burp Suite feature for writing custom filters to highlight interesting HTTP requests, such as those containing GraphQL queries with vulnerable
IDorStringscalar types.
Defensive Strategies: Secure GraphQL Development and Mitigation
Effectively defending against advanced GraphQL exploitation techniques, like secondary context attacks, requires a proactive, secure-by-default mindset that goes beyond reactive fixes. The root of many path traversal vulnerabilities lies in GraphQL’s own design, specifically with its default scalar types. Understanding and addressing these architectural weaknesses is the key to building resilient APIs.
The Problem with Default ID and String Scalar Types
A core vulnerability enabler for GraphQL path traversal is the overly permissive nature of the default ID and String scalar types. The GraphQL specification itself notes that the ID type “serializes the same as string,” merely signifying that it’s not intended to be human-readable. It performs no inherent format validation.
This means that from the GraphQL server’s perspective, both a legitimate UUID ("a1b2c3d4-...") and a path traversal payload ("../../api/v1/users/123") are perfectly valid inputs for a field typed as ID. This lack of built-in validation for insecure GraphQL scalar types forces the responsibility of sanitization and validation onto downstream services, which may not have the context to do so correctly, creating a significant security gap.
Adopt Custom, Type-Safe Scalers for Inherent Security
The most robust defense is to enforce strict type safety at the schema level itself. Instead of relying on generic String or ID types, developers should use precise, custom scalers. A highly recommended approach is to use a library like graphql-scalars, which provides pre-built, secure types for common data formats.
Key benefits of this approach include:
- Proactive Validation: By defining a field with a
UUIDscaler, any input that is not a valid UUID is rejected at the GraphQL layer before it can ever be passed to a backend microservice. This prevents path traversal payloads by design. - Improved Threat Modeling: A descriptive schema enhances security reviews. If a security engineer sees a field using a
URLscaler, it immediately signals the need to test for Server-Side Request Forgery (SSRF) and other URL-based vulnerabilities. - Incremental Adoption: Implementing custom scalers doesn’t require an immediate, full-scale refactor. Teams can adopt them incrementally—a “crawl, walk, run” approach—to progressively harden their GraphQL schema.
While an alternative mitigation, like using encodeURIComponent at the BFF/proxy layer, can prevent some path traversal, it’s a reactive fix. Adopting type-safe scalers is a “secure by default” architectural choice that fundamentally eliminates the vulnerability class.
The Danger of Hard-Coded Authorization at the Proxy
One of the most dangerous patterns that amplifies the impact of a secondary context attack is hard-coding authorization at the BFF or proxy layer. In this anti-pattern, the BFF receives a request from a client, and then adds a static, high-privilege authorization header (like a master API key or a service-to-service JWT) before forwarding the request to an internal microservice.
If an attacker can achieve path traversal, they don’t just control the request’s destination—they also inherit the powerful, hard-coded authorization token. This allows them to make authenticated requests to completely different microservices (e.g., payment or order services) as a trusted internal component, bypassing all user-level access controls. Security engineers must treat the presence of hard-coded credentials at the proxy as a critical risk that must be investigated and remediated.
Detection and Monitoring for Defenders
Because exploiting these vulnerabilities often involves significant probing—sending numerous requests with path traversal payloads to map out internal endpoints—it creates a detectable footprint. Defenders have a valuable opportunity to identify this activity by monitoring backend services for an abnormal spike in 404 Not Found or 503 Service Unavailable errors. Setting up alerts in observability platforms like DataDog for unusual error rates can serve as an early warning system for an ongoing attack.
Key Tools:
- graphql-scalars: A defense-focused library providing custom, type-safe GraphQL scalers (e.g.,
UUID) to enforce stricter input validation directly at the schema level, preventing insecure inputs.
Actionable Takeaways
- Assuming the default GraphQL `ID` scalar type provides any inherent security or validation. It serializes exactly like a `String` and will accept path traversal payloads, making it a primary vector for secondary context attacks if not properly validated downstream.
- Hard-coding a high-privilege authorization token at the BFF/proxy layer. This is a critical architectural flaw that allows an attacker who finds a path traversal vulnerability to inherit those privileges and make authenticated requests to sensitive internal microservices.
Common Pitfalls
- Assuming the default GraphQL `ID` scalar type provides any inherent security or validation. It serializes exactly like a `String` and will accept path traversal payloads, making it a primary vector for secondary context attacks if not properly validated downstream.
- Hard-coding a high-privilege authorization token at the BFF/proxy layer. This is a critical architectural flaw that allows an attacker who finds a path traversal vulnerability to inherit those privileges and make authenticated requests to sensitive internal microservices.
FAQ
What is a secondary context attack?
A secondary context attack occurs in architectures like Backend-for-Frontend (BFF) where a server receives a request and then makes a second request to an internal service. The attack happens when an attacker manipulates a parameter in the first request (e.g., a user ID) to control the destination or behavior of the second, trusted internal request, often via path traversal.
Why is the GraphQL ID scalar type so dangerous?
The default GraphQL ID type is dangerous because it provides no format validation. It serializes just like a String, meaning it will accept any text as valid input, including path traversal payloads (../../..). Developers often mistakenly assume it only accepts identifiers like UUIDs, creating a critical security blind spot.
How can I defend my GraphQL API against these attacks?
The most effective defense is to adopt a “secure by default” approach. Replace the generic ID and String scalar types with precise, custom types (e.g., UUID, Email) using a library like graphql-scalars. This enforces strict validation at the schema level. Additionally, avoid hard-coding high-privilege authentication tokens at the BFF/proxy layer to prevent privilege inheritance.
How do I find these vulnerabilities during a penetration test?
Start by identifying all GraphQL queries and mutations that use ID or String inputs. Fuzz these inputs with unexpected strings to see if they are reflected in backend error messages. If so, test for path traversal payloads (../test) to confirm you can control the internal request path. Use tools like Bambdas to automatically highlight these requests during testing.
Conclusion
Secondary context attacks represent a significant and often underestimated threat to modern applications, especially those leveraging GraphQL in a BFF architecture. The seemingly benign ID and String scalar types create an open door for path traversal, enabling attackers to pivot within a trusted network and chain vulnerabilities for devastating impact.
For offensive security engineers, this attack vector offers a rich area for exploration, rewarding a methodical approach that combines error-based reconnaissance with creative vulnerability chaining.
For defenders, security cannot be an afterthought. Proactive measures, such as adopting type-safe custom scalers and eliminating hard-coded credentials at the proxy layer, are essential to building resilient, secure-by-default systems. By understanding both the offensive methodology and the defensive patterns, teams can effectively mitigate this advanced GraphQL exploitation technique.
Tools & Other References: