Lab 31 — PayStream — IDOR in GraphQL Billing API
Lab 31 — PayStream — IDOR in GraphQL Billing API
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: graphql_introspection, hidden_field_discovery, idor_exploitation, tenant_isolation_bypass
Summary
Walkthrough of the CUSTOM Web Lab 31 — PayStream — IDOR in GraphQL Billing API. Task: Multi-tenant SaaS billing platform with GraphQL API for invoice management. Solution: Exploited IDOR in InvoiceDetails query to access other tenants' invoices and retrieve flag from hidden inter
Description
PayStream is a SaaS billing and subscription management platform used by thousands of organizations. Each tenant manages their invoices, payment methods, and billing history through a GraphQL-powered dashboard.
Your goal is to find and exploit an access control flaw in the billing system to retrieve a secret flag stored somewhere in the platform. The application exposes a rich GraphQL API for all billing operations — explore it carefully.
Pay attention to how invoice data is fetched and whether the API enforces proper tenant isolation. Not all data returned by the API is displayed in the UI.
Credentials: user@test.com / password123
English summary: A multi-tenant billing SaaS platform with GraphQL API. Given test credentials, the goal is to exploit broken access control to access data from other tenants and find the hidden flag.
Analysis
After authentication, the JWT token revealed the user context:
- userId: 3
- tenantId: 2
- tenantName: "Nova Digital"
GraphQL schema introspection revealed several queries:
InvoiceList- returns current user's invoices (no arguments)InvoiceDetails(id: Int!)- returns single invoice by ID (potential IDOR vector)PaymentMethods- payment method managementOrganizationInfo- tenant information
The critical vulnerability is in InvoiceDetails — it accepts an integer ID but does NOT verify that the requested invoice belongs to the authenticated user's tenant. Additionally, the Invoice type contains an internalNotes field that is not displayed in the UI but is accessible via direct API queries.
Solution
Step 1: Authentication
Logged in with provided credentials to obtain JWT token:
POST /api/auth/login
{"email": "user@test.com", "password": "password123"}
Step 2: GraphQL Schema Introspection
Discovered available queries and types:
graphql read-only • copy allowed{ __schema { queryType { fields { name args { name type { name kind ofType { name } } } } } } }
Step 3: Invoice Type Analysis
Introspected the Invoice type to find all available fields:
graphql read-only • copy allowed{ __type(name: "Invoice") { fields { name type { name kind ofType { name } } } } }
This revealed the hidden internalNotes field not shown in the UI.
Step 4: Identify Own Invoices
Queried InvoiceList to find own invoice IDs:
- Invoice #5 (Nova Digital)
- Invoice #6 (Nova Digital)
Step 5: IDOR Exploitation
Tested access to invoices from other tenants by iterating through IDs:
graphql read-only • copy allowed{ InvoiceDetails(id: 2) { id invoiceNumber internalNotes total tenant { name } } }
The query successfully returned invoice #2 from "StreamLine Corp" — a different tenant. The API did NOT verify tenant ownership before returning data.
Step 6: Flag Discovery
Invoice #2 from StreamLine Corp contained the flag in the internalNotes field:
FLAG{REDACTED}
Key Takeaways / Lessons
- Mastering graphql_introspection, hidden_field_discovery, idor_exploitation, tenant_isolation_bypass was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-04-26 • CUSTOM • Lab 31 — PayStream — IDOR in GraphQL Billing API • Difficulty: Medium