User Records Without Authentication
Introduction
This write-up covers a mass user enumeration vulnerability I discovered during a bug bounty engagement on a large e-commerce marketplace platform. The finding was confirmed valid by the security team, but closed as a duplicate — meaning another researcher had found and reported it before me. Duplicate or not, the methodology is worth documenting because this class of vulnerability is widespread and consistently underestimated.
The Target
The platform in question is a large consumer marketplace with tens of millions of registered users. I'll refer to it as [Platform] throughout this write-up in accordance with the program's disclosure policy. The tech stack — identified through response headers and JS bundle analysis — included a GraphQL API built on Python graphene with Relay cursor-based pagination, sitting behind Cloudflare.
Reconnaissance Phase
Subdomain Enumeration
Standard subdomain enumeration surfaced an `api.[platform].com` endpoint. Hitting the root returned 404. Appending `/graphql/` returned a response — the endpoint was live.
Introspection Check
First thing I always check on any GraphQL endpoint:
```graphql
{ __schema { queryType { fields { name } } } }
```Introspection was disabled. Expected on a production endpoint. This is where most people stop. I don't.
Field Enumeration via Error Messages
GraphQL validation errors are verbose by design — the spec requires descriptive error messages to help developers. This works against defenders because it allows attackers to enumerate type and field names without introspection. Sending a query against a non-existent field:
```graphql
{ email }
```Returns:
"Cannot query field 'email' on type 'Query'.
Did you mean 'users' or 'user'?"That single error message handed me two field names: `users` and `user`. The GraphQL API itself told me what to query next.
The Discovery
I queried the `users` field with a minimal selection set:
```graphql
{ users(first: 1) { totalCount } }
```Response:
```json
{
"data": {
"users": {
"totalCount": 57106775
}
}
}
```No authentication header. No session cookie. 57 million users returned on an unauthenticated request.
Confirming Scope and Impact
It's Live Production Data: Eight days later, I ran the same query. The count had increased to 57,316,627 — a delta of 209,852 new accounts. This is not test data. This is the live production user database.
Full Field Enumeration
Expanding the query to pull all available unauthenticated fields:
```graphql
{
users(first: 50) {
totalCount
pageInfo { hasNextPage endCursor }
edges {
node {
id
username
displayName
createdAt
isSeller
followerCount
followingCount
}
}
}
}
```Every field returned without authentication.
No Rate Limiting
Five consecutive requests. All returned HTTP 200 with full data. No throttling, no CAPTCHA, no block. Full database exportable at ~10 req/sec in approximately 32 hours.
The isSeller Boolean
This field is what elevates this beyond a simple enumeration issue. It trivially segments all merchant accounts from general users. Sellers on marketplace platforms handle payment information, shipping data, and customer communication. They are the highest-value targets for phishing on any marketplace. This query produces a complete seller list in one paginated export.

Why This Matters — The Technical Perspective
GraphQL Specific Risk:
- REST APIs fail closed — if you don't expose an endpoint, it doesn't exist. GraphQL fails open — every field you add to your schema is potentially queryable unless you explicitly add authorization checks at the field resolver level.
- The `users` query was likely built for an internal admin interface or a public user directory feature. At some point, someone added it to the root query type without a resolver-level authentication check. Because GraphQL doesn't have route-level auth middleware the way REST does, this goes unnoticed until someone queries it directly.
Why This Matters — The Business Perspective
For a marketplace platform, a complete export of all usernames, seller status, and account age has a direct business impact:
- Credential stuffing at scale: 57M usernames exported and tested against other platforms where users reuse passwords. Breach databases + this export = targeted account takeover campaigns.
- Seller targeting: Competitors can identify and poach sellers. Fraudsters can target merchants with payment scams. The isSeller field does the segmentation work automatically.
- Platform intelligence. Exact user counts and growth rates can be calculated from timestamped data. This is competitively sensitive information published during earnings calls—not in unauthenticated API responses.
Remediation
The fix is straightforward:
- Add authentication middleware at the GraphQL field resolver level for the `users` query
- If public user browsing is intended, remove `totalCount` from unauthenticated responses
- Implement query depth and rate limiting at the GraphQL layer
- Audit all root query fields for missing resolver-level authorization checks
Outcome
The security team confirmed the vulnerability and closed it as a duplicate — another researcher had submitted the same finding before me. The duplicate closure is frustrating, but it confirms the quality of the findings. The methodology works.
Takeaways
— Disabled introspection is not a security control — enumerate fields via error messages
— Always check `totalCount` on paginated GraphQL responses — it reveals dataset scale
— GraphQL requires per-resolver authorization checks, not just route-level middleware
— A delta between two observation points proves live production data — use it in your reports
Disclosed in accordance with the program's responsible disclosure policy. Program name withheld by agreement.