In October 2024, the OpenShift console exposed its GraphQL introspection without authentication to any client on the network. Not through a complex vulnerability, through default configuration. CVE-2024-50312 was exploited with a single POST containing {__schema{types{name}}}.
Disabling GraphQL introspection without addressing field suggestions, batching, and fingerprinting is security through obscurity. The entire schema remains enumerable with public tools, and most endpoints are passively indexed on Shodan before any active scanning begins. This post maps the systematic offensive recon that turns indexed endpoints into qualified attack surfaces.
Introspection in production: the scanner you left enabled
Most GraphQL deployments in production enable introspection without authentication. This is not theoretical speculation: CVEs published in 2024 and 2025 document the problem in critical production infrastructure, not obscure software.
CVE-2024-50312 affected the OpenShift Console with CVSS 3.1 score AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N (5.3, unauthenticated remote access, limited read). Any client on the network could enumerate queries, mutations, and types without credentials. The direct impact is full mapping of the internal API before any exploitation attempt. CVE-2025-53364 (versions prior to the March 2025 patch) repeated the pattern in Parse Server: public GraphQL schema with no session token or master key required.
CVE-2023-47643 hit SuiteCRM in versions prior to 8.4.2, where introspection was enabled by default in all production environments. The pattern across these 3 CVEs is not coincidental: Apollo Server v3 and earlier enable introspection in any environment by default, and frameworks derived from that base inherit the behavior.
The detection query takes 2 seconds:
POST /graphql HTTP/1.1
Content-Type: application/json
{"query":"{__schema{types{name}}}"}
A response containing a types array confirms full schema exposure. Apollo v4 disabled this default in production, but deployments with partial migrations or those using wrappers like Mercurius still require explicit configuration to disable introspection across all environments.
Field suggestions: the schema leaks even without introspection
Disabling introspection is necessary but not sufficient. Apollo's field suggestions mechanism leaks valid field names through error messages, enabling incremental schema reconstruction without any introspection query.
CVE-2024-37155 documented a direct bypass in OpenCTI below version 6.1.9. The secureIntrospectionPlugin blocked queries containing __schema via regex, but the regex failed on queries missing the \r\n characters. Removing those 2 characters restored full introspection without authentication, with CVSS 6.5. The lesson: a security control based on string pattern matching is fragile by definition.
Clairvoyance, developed by Nikita Stupin, automates schema reconstruction via field stuffing when introspection is blocked:
python3 clairvoyance.py \
-t https://api.example.com/graphql \
-w /usr/share/seclists/Discovery/GraphQL/graphql-wordlist.txt \
-o schema.json
The process exploits responses like "Cannot query field user. Did you mean userProfile?" to confirm valid fields one by one. The Escape Technologies wordlist covers 50,000 common field names across modern GraphQL APIs. The resulting schema is indistinguishable from one obtained via direct introspection.
Apollo Server v4 introduced the hideSchemaDetailsFromClientErrors option to suppress these suggestions. Most deployments do not enable this option explicitly, either due to lack of awareness or because the option is not prominently documented. Clairvoyance is the attacker's default fallback when introspection is blocked.
Engine fingerprinting: graphw00f as pre-exploit recon
Identifying the GraphQL engine before exploiting determines which protections are active by default. Apollo and Yoga have different defaults than Hasura and WPGraphQL, and that difference determines which techniques work without additional defender-side configuration.
graphw00f, maintained by Dolev Farhi, detects 35 or more engines by sending a mix of benign and malformed queries:
graphw00f -d -t https://api.target.com/graphql
Each engine responds with unique error signatures that allow precise identification. The output includes a direct link to the GraphQL Threat Matrix, which maps which protections each engine activates by default, which are optional, and which are entirely absent in the default configuration. That mapping saves testing time by eliminating vectors the engine already mitigates natively.
InQL (Burp Suite GUI plugin — no CLI equivalent) from Doyensec integrates fingerprinting into Burp Suite and auto-generates queries and mutations from the discovered schema. Common paths requiring probing include /graphql, /api/graphql, /graphiql, /playground, /altair, /v1/graphql, /query, and /gql. The combination of graphw00f with InQL covers engine detection and payload generation adapted to the target's specific security profile. For WordPress targets, WPGraphQL exposes user data and taxonomies that frequently require no authentication.
Batching and aliasing: one request, native rate limit bypass
GraphQL batching turns request-count-based rate limiting into an ineffective control. A single HTTP POST can encapsulate hundreds of distinct operations. This is in the protocol specification, not a bug or edge case.
CVE-2024-39895 affected Directus with CVSS 6.5. Any authenticated user multiplied database load linearly via alias count, degrading service for every other user on the system. The vector required no elevated privileges: a standard account was sufficient. HackerOne #2207248, paid US$5,000 by Shopify, demonstrated IDOR via BillingDocumentDownload and BillDetails queries with IDs enumerated in batch through a single HTTP request.
The aliasing brute-force bypass is the highest-impact variant for authentication testing:
mutation {
a1: login(username: "admin", password: "password1") { token }
a2: login(username: "admin", password: "password2") { token }
a3: login(username: "admin", password: "password3") { token }
a4: login(username: "admin", password: "password4") { token }
}
The request-count rate limiter sees 1 request. The application processes 100 authentication attempts in parallel. Tools like Burp Intruder generate these payloads automatically from password wordlists. Real mitigation requires complexity limiting implemented at the code level, with libraries like graphql-depth-limit, not just configuration at the API gateway.
Passive OSINT: GraphQL endpoints indexed on Shodan before you scan
GraphQL endpoints are passively indexed by Shodan and Censys. An attacker maps the attack surface without ever sending a packet to the target server.
Shodan queries to identify exposed endpoints:
http.body:"{\"data\":" http.body:"graphql" port:443
product:"GraphQL" http.status:200
Censys complements this with full-text search on HTTP response bodies and certificate SAN correlation to map associated subdomains. The difference between the two databases is ASN coverage: Shodan has greater depth in IPv4, Censys has better TLS coverage. Combining both eliminates most blind spots in passive discovery.
intel.mago.team (MAGO team tool) automates this pipeline as a passive spell: queries the Shodan API by org or ASN and confirms active GraphQL endpoints with an httpx probe before any direct interaction with the target.
Active validation after passive discovery confirms live endpoints:
httpx -l domains.txt \
-path /graphql \
-mc 200 \
-H "Content-Type: application/json" \
-d '{"query":"{__typename}"}' \
-o live-graphql.txt
Goctopus from Escape Technologies combines subdomain enumeration, path brute-forcing, and introspection attempts into a single automated pipeline. Crossing results with passive reconnaissance via Shodan and Censys extends coverage before any direct interaction with the target.
The five techniques above work independently. The following playbook chains all of them in progressive sequence, from passive signal to full schema.
Offensive playbook: from discovery to full schema in 5 steps
A structured GraphQL recon flow is reproducible and automatable against any target with a SaaS presence or public API. The 5 steps cover everything from passive OSINT to full schema enumeration.
Step 1 - Passive: Shodan and Censys queries by target org or ASN. Subfinder and amass for subdomains correlated via certificate transparency logs. Certificate logs are especially useful for discovering staging subdomains with exposed GraphQL that do not appear in public DNS.
Step 2 - Probe: httpx with a GraphQL path wordlist from SecLists confirming response to {__typename}. Any 200 response with {"data":{"__typename":"Query"}} confirms an active GraphQL endpoint. 400 responses with error messages also confirm GraphQL, just with a malformed query.
Step 3 - Fingerprint: graphw00f identifies the engine and returns a protection profile via the GraphQL Threat Matrix. The result determines whether field suggestions, depth limiting, and batching are active by default on the target. Different engines require different payloads for the same vectors.
Step 4 - Introspection: direct attempt with {__schema{types{name}}}. If blocked, try variants: space after __schema, removal of \r\n, GET request instead of POST, character encoding in the query. CVE-2024-37155 showed that regex-based blocks fail on at least 1 whitespace variant.
Step 5 - Enum: if introspection fails across all variants, Clairvoyance with Escape Technologies' graphql-wordlist reconstructs the schema via field suggestions. The resulting schema feeds into InQL for generating test payloads adapted to the target. From there, authorization testing, batching abuse, and IDOR follow the same pattern as any audited REST API.
GraphQL is no longer niche. It is the default API for modern SaaS, fintech, and e-commerce platforms. The systematic offensive recon outlined here turns every endpoint passively discovered on Shodan into a qualified target with a mapped schema before the first login attempt.
Top comments (0)