first commit
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user