277 lines
7.8 KiB
Markdown
277 lines
7.8 KiB
Markdown
---
|
|
name: graphql
|
|
description: GraphQL security testing covering introspection, resolver injection, batching attacks, and authorization bypass
|
|
---
|
|
|
|
# GraphQL
|
|
|
|
Security testing for GraphQL APIs. Focus on resolver-level authorization, field/edge access control, batching abuse, and federation trust boundaries.
|
|
|
|
## Attack Surface
|
|
|
|
**Operations**
|
|
- Queries, mutations, subscriptions
|
|
- Persisted queries / Automatic Persisted Queries (APQ)
|
|
|
|
**Transports**
|
|
- HTTP POST/GET with `application/json` or `application/graphql`
|
|
- WebSocket: graphql-ws, graphql-transport-ws protocols
|
|
- Multipart for file uploads
|
|
|
|
**Schema Features**
|
|
- Introspection (`__schema`, `__type`)
|
|
- Directives: `@defer`, `@stream`, custom auth directives (@auth, @private)
|
|
- Custom scalars: Upload, JSON, DateTime
|
|
- Relay: global node IDs, connections/cursors, interfaces/unions
|
|
|
|
**Architecture**
|
|
- Federation (Apollo, GraphQL Mesh): `_service`, `_entities`
|
|
- Gateway vs subgraph authorization boundaries
|
|
|
|
## Reconnaissance
|
|
|
|
**Endpoint Discovery**
|
|
```
|
|
POST /graphql {"query":"{__typename}"}
|
|
POST /api/graphql {"query":"{__typename}"}
|
|
POST /v1/graphql {"query":"{__typename}"}
|
|
POST /gql {"query":"{__typename}"}
|
|
GET /graphql?query={__typename}
|
|
```
|
|
|
|
Check for GraphiQL/Playground exposure with credentials enabled (cross-origin with cookies can leak data via postMessage bridges).
|
|
|
|
**Schema Acquisition**
|
|
|
|
If introspection enabled:
|
|
```graphql
|
|
{__schema{types{name fields{name args{name}}}}}
|
|
```
|
|
|
|
If disabled, infer schema via:
|
|
- `__typename` probes on candidate fields
|
|
- Field suggestion errors (submit near-miss names to harvest suggestions)
|
|
- "Expected one of" errors revealing enum values
|
|
- Type coercion errors exposing field structure
|
|
- Error taxonomy: different codes for "unknown field" vs "unauthorized field" reveal existence
|
|
|
|
**Schema Mapping**
|
|
|
|
Map: root operations, object types, interfaces/unions, directives, custom scalars. Identify sensitive fields: email, tokens, roles, billing, API keys, admin flags, file URLs. Note cascade paths where child resolvers may skip auth under parent assumptions.
|
|
|
|
## Key Vulnerabilities
|
|
|
|
### Authorization Bypass
|
|
|
|
**Field-Level IDOR**
|
|
|
|
Test with aliases comparing owned vs foreign objects in single request:
|
|
```graphql
|
|
query {
|
|
own: order(id:"OWNED_ID") { id total owner { email } }
|
|
foreign: order(id:"FOREIGN_ID") { id total owner { email } }
|
|
}
|
|
```
|
|
|
|
**Edge/Child Resolver Gaps**
|
|
|
|
Parent resolver checks auth, child resolver assumes it's already validated:
|
|
```graphql
|
|
query {
|
|
user(id:"FOREIGN") {
|
|
id
|
|
privateData { secrets } # Child may skip auth check
|
|
}
|
|
}
|
|
```
|
|
|
|
**Relay Node Resolution**
|
|
|
|
Decode base64 global IDs, swap type/id pairs:
|
|
```graphql
|
|
query {
|
|
node(id:"VXNlcjoxMjM=") { ... on User { email } }
|
|
}
|
|
```
|
|
Ensure per-type authorization is enforced inside resolvers. Verify connection filters (owner/tenant) apply before pagination; cursor tampering should not cross ownership boundaries.
|
|
|
|
**Mutation Bypass**
|
|
- Probe mutations for partial updates bypassing validation (JSON Merge Patch semantics)
|
|
- Test mutations that accept extra fields passed to downstream logic
|
|
|
|
### Batching & Alias Abuse
|
|
|
|
**Enumeration via Aliases**
|
|
```graphql
|
|
query {
|
|
u1:user(id:"1"){email}
|
|
u2:user(id:"2"){email}
|
|
u3:user(id:"3"){email}
|
|
}
|
|
```
|
|
Bypasses per-request rate limits; exposes per-field vs per-request auth inconsistencies.
|
|
|
|
**Array Batching**
|
|
|
|
If supported (non-standard), submit multiple operations to achieve partial failures and bypass limits.
|
|
|
|
### Input Manipulation
|
|
|
|
**Type Confusion**
|
|
```
|
|
{id: 123} vs {id: "123"}
|
|
{id: [123]} vs {id: null}
|
|
{id: 0} vs {id: -1}
|
|
```
|
|
|
|
**Duplicate Keys**
|
|
```json
|
|
{"id": 1, "id": 2}
|
|
```
|
|
Parser precedence varies; may bypass validation. Also test default argument values.
|
|
|
|
**Extra Fields**
|
|
|
|
Send unexpected keys in input objects; backends may pass them to resolvers or downstream logic.
|
|
|
|
### Cursor Manipulation
|
|
|
|
Decode cursors (usually base64) to:
|
|
- Manipulate offsets/IDs
|
|
- Skip filters
|
|
- Cross ownership boundaries
|
|
|
|
### Directive Abuse
|
|
|
|
**@defer/@stream**
|
|
```graphql
|
|
query {
|
|
me { id }
|
|
... @defer { adminPanel { secrets } }
|
|
}
|
|
```
|
|
May return gated data in incremental delivery. Confirm server supports incremental delivery.
|
|
|
|
**Custom Directives**
|
|
|
|
@auth, @private and similar directives often annotate intent but do not enforce—verify actual checks in each resolver path.
|
|
|
|
### Complexity Attacks
|
|
|
|
**Fragment Bombs**
|
|
```graphql
|
|
fragment x on User { friends { ...x } }
|
|
query { me { ...x } }
|
|
```
|
|
Test depth/complexity limits, query cost analyzers, timeouts.
|
|
|
|
**Wide Selection Sets**
|
|
|
|
Abuse selection sets and fragments to force overfetching of sensitive subfields.
|
|
|
|
### Federation Exploitation
|
|
|
|
**SDL Exposure**
|
|
```graphql
|
|
query { _service { sdl } }
|
|
```
|
|
|
|
**Entity Materialization**
|
|
```graphql
|
|
query {
|
|
_entities(representations:[
|
|
{__typename:"User", id:"TARGET_ID"}
|
|
]) { ... on User { email roles } }
|
|
}
|
|
```
|
|
Gateway may enforce auth; subgraph resolvers may not. Look for cross-subgraph IDOR via inconsistent ownership checks.
|
|
|
|
### Subscription Security
|
|
|
|
- Authorization at handshake only, not per-message
|
|
- Subscribe to other users' channels via filter args
|
|
- Cross-tenant event leakage
|
|
- Abuse filter args in subscription resolvers to reference foreign IDs
|
|
|
|
### Persisted Query Abuse
|
|
|
|
- APQ hashes leaked from client bundles
|
|
- Replay privileged operations with attacker variables
|
|
- Hash bruteforce for common operations
|
|
- Validate hash→operation mapping enforces principal and operation allowlists
|
|
|
|
### CORS & CSRF
|
|
|
|
- Cookie-auth with GET queries enables CSRF on mutations via query parameters
|
|
- GraphiQL/Playground cross-origin with credentials leaks data
|
|
- Missing SameSite and origin validation
|
|
|
|
### File Uploads
|
|
|
|
GraphQL multipart spec:
|
|
- Multiple Upload scalars
|
|
- Filename/path traversal tricks
|
|
- Unexpected content-types, oversize chunks
|
|
- Server-side ownership/scoping for returned URLs
|
|
|
|
## WAF Evasion
|
|
|
|
**Query Reshaping**
|
|
- Comments and block strings (`"""..."""`)
|
|
- Unicode escapes
|
|
- Alias/fragment indirection
|
|
- JSON variables vs inline args
|
|
- GET vs POST vs `application/graphql`
|
|
|
|
**Fragment Splitting**
|
|
|
|
Split fields across fragments and inline spreads to avoid naive signatures:
|
|
```graphql
|
|
fragment a on User { email }
|
|
fragment b on User { password }
|
|
query { me { ...a ...b } }
|
|
```
|
|
|
|
## Bypass Techniques
|
|
|
|
**Transport Switching**
|
|
```
|
|
Content-Type: application/json
|
|
Content-Type: application/graphql
|
|
Content-Type: multipart/form-data
|
|
GET with query params
|
|
```
|
|
|
|
**Timing & Rate Limits**
|
|
- HTTP/2 multiplexing and connection reuse to widen timing windows
|
|
- Batching to bypass rate limits
|
|
|
|
**Naming Tricks**
|
|
- Case/underscore variations
|
|
- Unicode homoglyphs (server-dependent)
|
|
- Aliases masking sensitive field names
|
|
|
|
**Cache Confusion**
|
|
- CDN caching without Vary on Authorization
|
|
- Variable manipulation affecting cache keys
|
|
- Redirects and 304/206 behaviors leaking partial responses
|
|
|
|
## Testing Methodology
|
|
|
|
1. **Fingerprint** - Identify endpoints, transports, stack (Apollo, Hasura, etc.), GraphiQL exposure
|
|
2. **Schema mapping** - Introspection or inference to build complete type graph
|
|
3. **Principal matrix** - Collect tokens for unauth, user, premium, admin roles with at least one valid object ID per subject
|
|
4. **Field sweep** - Test each resolver with owned vs foreign IDs via aliases in same request
|
|
5. **Transport parity** - Verify same auth on HTTP, WebSocket, persisted queries
|
|
6. **Federation probe** - Test `_service` and `_entities` for subgraph auth gaps
|
|
7. **Edge cases** - Cursors, @defer/@stream, subscriptions, file uploads
|
|
|
|
## Validation Requirements
|
|
|
|
- Paired requests (owner vs non-owner) showing unauthorized access
|
|
- Resolver-level bypass: parent checks present, child field exposes data
|
|
- Transport parity proof: HTTP and WebSocket for same operation
|
|
- Federation bypass: `_entities` accessing data without subgraph auth
|
|
- Minimal payloads with exact selection sets and variable shapes
|
|
- Document exact resolver paths that missed enforcement
|