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
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