← Back to Writeups
CUSTOMMediumWeb

Lab 31 — PayStream — IDOR in GraphQL Billing API

XESXOR9/10/20263 min read
#web#custom#medium#graphql_introspection#hidden_field_discovery#idor_exploitation#tenant_isolation_bypass

Lab 31 — PayStream — IDOR in GraphQL Billing API

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-04-26 | Status: Solved Techniques: graphql_introspection, hidden_field_discovery, idor_exploitation, tenant_isolation_bypass

Summary

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 internalNotes field.

Lab 31 — PayStream — IDOR in GraphQL Billing API — HackAdvisor

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 management
  • OrganizationInfo - 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
{ __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
{ __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
{ 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}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍