For the complete documentation index, see llms.txt. This page is also available as Markdown.

Cursed

Mystery Challenge (Medium) - by Leighlin Gunner Ramsay

The following post by 0xb0b is licensed under CC BY 4.0


Scenario

FleetLine sells itself on hard tenant isolation: your org sees your org, and nobody else's. Westline believes it, Ranjay believes it, the sales deck swears by it. But isolation is only ever as strong as the weakest place it is actually enforced, and FleetLine is a bigger application than the tidy dashboard lets on.

Summary

Summary

The target is FleetLine, a fleet-management platform that promises hard tenant isolation between organizations. Recon through Burp reveals the site runs on Apollo Server with GraphQL, where introspection is enabled. The dashboard loads work orders via cursor-based pagination, and a Load more button passes an after cursor that decodes to {"id":815,"org":"westline"}.

The core technique is cursor forgery for cross-tenant access. Introspection reveals hidden internalNotes and attachment fields on WorkOrder that the frontend does not request. Querying our own orders uncovers a reference to another org: apex-haul. Direct ID lookup via workOrder(id) is properly scoped and returns null for other orgs' records, but the workOrders pagination endpoint blindly trusts the org field in the cursor without validating it against the authenticated user. Forging a cursor with {"id":0,"org":"apex-haul"} and passing it as a GraphQL variable pivots into apex-haul's work orders, exposing decommissioned admin credentials in internalNotes and the final flag in attachment.

Attack Path

Visit site → intercept GraphQL requests in Burp ↓ Load more sends pagination cursor → decode: {"id":815,"org":"westline"} ↓ Introspection → discover hidden fields: internalNotes, attachment ↓ Own work orders leak another org name: "apex-haul" ↓ Forge cursor: {"id":0,"org":"apex-haul"} → base64 ↓ Server trusts cursor org → returns cross-tenant work orders ↓ internalNotes + attachment → leaked credentials + flag

Recon

We start by visiting the site and intercepting requests in Burp Suite. The application makes GraphQL requests to a single endpoint.

It looks like we only have one main site available listing work orders. But we are able to load more.

When we visit the respective pages, we see that a GraphQL request is made and that we receive the result back as JSON.

The query is a paginated GraphQL query that requests 3 work orders at a time, returning each order's id, title, vehicle, and status along with a cursor (endCursor) and a flag (hasNextPage) to fetch the next page.

Clicking Load more sends a second request with a pagination cursor:

The Load more query is almost the same query but now passing the previous page's cursor in the after variable to fetch the next batch of work orders starting after ID 815.

That cursor is base64. We decode it to understand the structure:

We continue by enumerating the GraphQL schema. We try introspection and see it is enabled by dumping the full schema. The interesting types are Query with root fields: me, workOrder, workOrders, User (username, name, org, role), and WorkOrder, which has internalNotes and attachment fields that the frontend did not request.

The internalNotes and attachment fields are not requested by the frontend, so those might be the hidden sensitive data. We check who we are and see we are ranjay in org westline with the role technician.

Exploit

We request all fields on our own work orders, including the hidden internalNotes and attachment.

We get 4 work orders: 810, 812, 815, 818. All belonging to westline. One leaks another org name in its notes:

"Truck 14 transferred in from the apex-haul fleet last quarter"

We try direct access to other orgs work orders via workOrder(id), but the resolver seems to enforce some sort of scoping and returns null.

However, during testing we discover that the workOrders pagination endpoint trusts the cursors org field without validating it against the authenticated user. We forge a cursor for apex-haul.

We pass the forged cursor using GraphQL variables to avoid base64 escaping issues. The server returns apex-haul work orders, including credentials in an internal note and the flag:

Last updated