← Back to all reports

Self-Created Guest Account Reads a Travel Platform's Entire Traveller Directory

Reported Jun 24, 2026
Severity High
Platform Web / API
Vulnerability Class Broken Access Control (CWE-284)
Target Type Corporate Travel & Expense Platform
Impact 800,000+ travellers' names, emails and profiles exposed

The Risk

Anyone with a free, ordinary account on this corporate travel service could create a second "guest" login and then use it to download a directory of more than 800,000 travellers, including their names, email addresses and profile details. The guest account has no special powers and no admin rights, yet it could pull the whole list or look up any single person by name or email. That turns the service into a ready-made targeting and phishing resource against hundreds of thousands of real people, many of whom signed up with their work email at well-known companies, universities, and government bodies.

The Vulnerability

The platform serves both corporate customers (each in its own private company tenant) and a large shared pool of individual, self-serve travellers. A standard traveller account can create a login-capable external guest. The flaw is that the created guest is placed into the shared individual-accounts pool regardless of who created it, and the directory endpoint that lists that pool performs no authorisation check beyond "are you authenticated".

The guest is roleless by every measure the platform exposes: it has no roles, no permissions, and the platform's own access check for the feature returns false. The directory endpoint returns user records anyway. Each record includes the user's unique id, email, full name, company domain, office fields, policy level, invitee type, enabled status, and a profile-picture URL.

Crucially, this is the global pool of individual travellers, not a single company's managed employee list. A corporate tenant's own member list stays correctly scoped to that tenant. The exposure is the entire self-serve traveller base, which numbered more than 800,000 records at the time of testing.

The Attack

The end-to-end chain needs only one ordinary, non-admin traveller account (free self-registration) and runs front-end, through the platform's own normal flows.

Create a login-capable guest

From the attacker's standard account, create an enabled guest pointed at an inbox the attacker controls. The platform returns an enabled guest object:

POST /api/guests
{ "givenName":"Guest", "familyName":"Proof",
  "email":"[email protected]" }
// -> 200, guest created, enabled: true

Take over the guest via the normal password flow

The attacker triggers the platform's standard "forgot password" flow for the guest email, receives the reset link in the controlled inbox, sets a password, and logs in as the guest. No developer tools and no tricks, just the product's own front-end.

Read the directory as the roleless guest

Logged in as the guest, the directory endpoint returns user records and a total count of the whole pool, even though the guest's own access check says it has no access:

GET /api/directory?page=0&size=5
// guest token: roles [], permissions [], hasAccess: false
// response: 200, page.totalElements: 800,000+
// each record: id, email, fullName, companyDomain, office, enabled ...

Paging to the very last offset still returns a real record, confirming the count is a genuine, fully reachable dataset and not an inflated counter.

Targeted lookup, not just a bulk dump

The endpoint honours a search parameter (the same one the web interface uses) that token-matches name and email. A full email address resolves to exactly one person; a common surname returns thousands of records that paginate. An exact-record lookup by user id is also accepted. So an attacker can pinpoint a specific named individual or harvest by surname, in addition to dumping the whole pool.

The Impact

An arbitrary holder of one free account can read, in bulk or by precision lookup, the personal data of more than 800,000 individual travellers:

  • Mass personal data at scale. Names, email addresses, company domains, profile photos, and travel-policy metadata for the entire self-serve pool.
  • Precision targeting. A single email returns exactly that person; a surname returns thousands. The directory is a phishing and reconnaissance resource against named people.
  • Real organisations in the data. Many individuals registered with work email addresses, so corporate, university, and government email domains across hundreds of distinct organisations appear in the records, even though these are the individuals' personal accounts, not their employers' directories.
  • Not an inert identity. The created guest can itself create another enabled guest, so the attacker's foothold is self-sustaining.

The rating is High: it is a large-scale personal-data exposure and a targeted-lookup oracle, reachable by any free account with no special role, but the data is contact and profile fields rather than credentials or payment data.

One operational note observed during testing: the platform reactively disables freshly created guest accounts a few hours after creation. That is after-the-fact cleanup, not a fix; a newly minted guest reproduces the full chain immediately, so an attacker simply mints a fresh guest each session.

Remediation

  • Enforce an authorisation check on the directory endpoint: a roleless guest with no access (the platform's own access check already returns false) must not receive directory records. Honour that check at the data layer, not just in the interface.
  • Do not place externally created guests into the global shared traveller pool. Scope a guest's visibility to the context that created it.
  • Apply rate limiting and anomaly detection on directory reads and search queries to prevent bulk harvesting and surname-sweeping.
  • Treat reactive disabling of guests as a stopgap only; the underlying authorisation gap is the issue to close.