Abusing GraphQL to Infiltrate Organizations and Steal Confidential Data

While exploring a web app’s GraphQL API, I came across something interesting — a function that didn’t just leak information, but actually let me join private organizations without any approval. What started as simple curiosity turned into a full-blown account and data breach scenario.

Background

The application in question allows teams and organizations to collaborate privately within workspaces. Users can be invited to organizations, and permissions determine what each user can access — so, ideally, no outsider should ever see or touch internal data.

But as it turns out, the GraphQL API had a completely different idea.

The Discovery

I began by browsing through the app’s front-end source files and noticed a few GraphQL operations that weren’t directly referenced in the UI. That’s always worth checking.

Two stood out immediately:

  • joinOrganizationByEmailDomain (mutation)
  • GetPublicOrganizationsByEmailDomain (query)

They sounded harmless — but putting them together revealed something dangerous.

Step 1 — Leaking Organization IDs

First, the following GraphQL query could be sent with nothing more than a company’s email domain:

[
  {
    "operationName": "GetPublicOrganizationsByEmailDomain",
    "variables": {"emailDomain": "targetcompany.com"},
    "query": "query GetPublicOrganizationsByEmailDomain($emailDomain: String!) 
    { publicOrganizationsByEmailDomain(emailDomain: $emailDomain) 
     { _id members { user { email } } name } }"
  }
]

The response contained a full list of organizations using that domain — including their organization IDs, names, and member email addresses.

At this point, I could already enumerate users and internal team structures without ever logging in.

Step 2 — Joining Any Organization

Next, I tried the joinOrganizationByEmailDomain mutation:

[
  {
    "operationName": "joinOrganizationByEmailDomain",
    "variables": {"organizationId": "<OrganizationID>"},
    "query": "mutation joinOrganizationByEmailDomain($organizationId: ID!) 
            { joinOrganizationByEmailDomain(organizationId: $organizationId) 
            { _id __typename } }"
  }
]

Replacing <OrganizationID> with the ID from the previous query granted instant membership into the target organization — no approval, no invitation, no checks.

Just like that, I was inside.

Step 3 — Data Exfiltration

Once added to the organization, all internal resources became accessible: design files, project assets, and shared documents.

Even worse, this could be chained with another vulnerability I had previously reported — one that allowed exporting organization data to a separate workspace under my control.

That meant I could:

  • Access and copy proprietary assets
  • Transfer them to my own organization
  • Fully exfiltrate sensitive information

Essentially, a total data breach.

The Impact

This wasn’t just a minor privacy bug. It was a complete breakdown of the organization access model.

With a single unauthenticated query, I could:

  • Enumerate company members and structure
  • Join any private organization
  • Access and export confidential data

The implications were serious: intellectual property theft, business espionage, and reputational damage — all possible through two GraphQL endpoints.

Root Cause

There were two core problems:

  1. Overly permissive data exposureGetPublicOrganizationsByEmailDomain leaked internal identifiers and member details.
  2. Missing authorization controlsjoinOrganizationByEmailDomain trusted client-side logic and didn’t verify the legitimacy of join requests.

Combined, these turned a basic information disclosure into full unauthorized access.

Recommendations

To fix this, I recommended:

  • Restricting GetPublicOrganizationsByEmailDomain to authenticated and domain-verified users.
  • Adding proper authorization checks to joinOrganizationByEmailDomain.
  • Avoiding exposure of organization IDs and member lists through public queries.
  • Performing a broader audit for insecure internal mutations.

Final Thoughts

What I love about GraphQL is also what makes it dangerous — its flexibility. When every field and mutation is a potential attack surface, one small oversight can cascade into massive compromise.

This was one of those cases where two small issues combined to form something far more severe. It’s a good reminder that in complex APIs, context is everything — and attackers only need one misconfiguration to find the full chain.