August 27, 2026
Unauthenticated Full User Directory PII Dump — 103K+ Records Exposed
Introduction
By redhunter01
11 min read
Introduction
One of the most important lessons I've learned while testing modern web applications is that an endpoint returning 400 Bad Request is not necessarily an endpoint that is protected.
During an API assessment, I encountered an identity-related endpoint that appeared to sit behind an authentication gateway. The endpoint was located under an /open/ path, and requests without the expected tenant-routing information initially failed with a 400.
Instead of assuming the endpoint was protected, I investigated why the request was failing.
That led to a much more serious finding:
A single unauthenticated HTTP request could retrieve the entire user directory of the tenant.
The exposed dataset contained more than 103,000 user records, institutional email addresses, internal database identifiers, organizational roles, last-login timestamps, and more than 28,000 push-notification device registrations.
No account, session, cookie, or bearer token was required.
1. The Mindset
When testing APIs, I try not to think only about what the frontend allows me to do.
Instead, I ask:
What APIs does the application use?
What services sit behind the gateway?
Which routes are authenticated?
Which routes are intentionally public?
What happens if I call the API directly?
What does the server actually use to make its authorization decision?What APIs does the application use?
What services sit behind the gateway?
Which routes are authenticated?
Which routes are intentionally public?
What happens if I call the API directly?
What does the server actually use to make its authorization decision?This distinction is important because modern applications often look like:
Browser
|
v
CDN / Reverse Proxy
|
v
API Gateway
|
+---- Authentication
|
+---- Routing
|
+---- Authorization
|
v
Microservices
|
+---- Identity Service
+---- User Service
+---- Notification Service
+---- Other APIsBrowser
|
v
CDN / Reverse Proxy
|
v
API Gateway
|
+---- Authentication
|
+---- Routing
|
+---- Authorization
|
v
Microservices
|
+---- Identity Service
+---- User Service
+---- Notification Service
+---- Other APIsA vulnerability in the boundary between the gateway and a backend service can expose functionality that the frontend never intended to make publicly accessible.
2. Finding the Identity API
During reconnaissance, I identified an API structure associated with an identity service:
/unifyd-gateway/api/unifydidentity//unifyd-gateway/api/unifydidentity/One namespace immediately stood out:
/unifyd-gateway/api/unifydidentity/open//unifyd-gateway/api/unifydidentity/open/The presence of /open/ does not automatically mean a vulnerability exists.
Applications legitimately expose public endpoints such as:
/open/login
/open/config
/open/health
/open/metadata/open/login
/open/config
/open/health
/open/metadataThe important question is:
What functionality is actually available under the supposedly open namespace?
I found a collection endpoint:
GET /unifyd-gateway/api/unifydidentity/open/usersGET /unifyd-gateway/api/unifydidentity/open/usersA /users collection endpoint is immediately interesting because it potentially represents access to a large amount of identity data.
3. The First Interesting Response
My initial request without additional routing information returned:
HTTP/1.1 400 Bad RequestHTTP/1.1 400 Bad RequestAt first glance, it would have been easy to conclude:
Endpoint is protected.Endpoint is protected.But there was a problem with that assumption.
A 400 does not normally mean:
You are not authenticated.You are not authenticated.It means the server rejected something about the request itself.
So I changed my question from:
"Is this endpoint protected?"
to:
"What condition causes the server to return 400?"
That distinction turned out to be critical.
4. Understanding the Tenant Header
The API required a tenant-routing header:
x-tenant-id: mymohawkcollegeu8x-tenant-id: mymohawkcollegeu8The important thing was determining whether this value represented:
AuthenticationAuthenticationor simply:
Routing / tenant selectionRouting / tenant selectionThe tenant identifier was not a secret credential. It was publicly discoverable through client-side application resources.
That meant an external user could determine the correct routing value without having an account.
The request flow effectively became:
Attacker
|
| x-tenant-id
v
Gateway
|
| route request to tenant
v
Identity API
|
| authentication?
| NO
v
User repositoryAttacker
|
| x-tenant-id
v
Gateway
|
| route request to tenant
v
Identity API
|
| authentication?
| NO
v
User repositoryThis was the first major indication that the tenant header was not functioning as an authentication mechanism.
5. Authentication Differential Testing
Rather than relying on one request, I tested several authentication states.
The resulting matrix was:
RequestResultTenant header only200 OK — full directory returnedTenant header + invalid bearer200 OK — full directory returnedValid bearer without tenant header400 — routing failureNo headers400 — routing failure
The most important comparison was:
No authentication
+
Correct tenant routing
=
200 OKNo authentication
+
Correct tenant routing
=
200 OKwhile:
Authentication
+
Missing tenant routing
=
400Authentication
+
Missing tenant routing
=
400This strongly indicated that the tenant header was being used for routing rather than authorization.
An invalid bearer token did not change the result either.
6. Minimal Proof of Concept
The vulnerable request required only:
GET /unifyd-gateway/api/unifydidentity/open/users HTTP/1.1
Host: target.example
x-tenant-id: mymohawkcollegeu8GET /unifyd-gateway/api/unifydidentity/open/users HTTP/1.1
Host: target.example
x-tenant-id: mymohawkcollegeu8There was no:
Authorization: Bearer ...Authorization: Bearer ...No session cookie.
No authenticated account.
No CSRF token.
The server responded with:
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunkedHTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunkedThe response contained user records directly.
This was enough to establish the authentication bypass.
The next question was:
How much data is actually exposed?
7. The API Returned the Raw User Entity
The response was not a minimal public directory.
Individual records contained fields similar to:
{
"id": "...",
"firstName": "...",
"lastName": "...",
"role": [
"AADStudents",
"AllUsers",
"Public"
],
"secondaryEmail": "...",
"tenant": "...",
"email": "...",
"channels": [
"email",
"push"
],
"lastlogindate": "...",
"devices": [
{
"type": "Android",
"id": "..."
}
]
}{
"id": "...",
"firstName": "...",
"lastName": "...",
"role": [
"AADStudents",
"AllUsers",
"Public"
],
"secondaryEmail": "...",
"tenant": "...",
"email": "...",
"channels": [
"email",
"push"
],
"lastlogindate": "...",
"devices": [
{
"type": "Android",
"id": "..."
}
]
}This was particularly interesting because the application was exposing the underlying identity entity rather than a carefully designed public representation.
Conceptually, the intended design might have been:
Public Directory
|
+-- Name
+-- DepartmentPublic Directory
|
+-- Name
+-- DepartmentBut the actual API behaved more like:
Database Entity
|
+-- Internal ID
+-- Name
+-- Email
+-- Secondary Email
+-- Roles
+-- Login Timestamp
+-- Device Registrations
+-- Other MetadataDatabase Entity
|
+-- Internal ID
+-- Name
+-- Email
+-- Secondary Email
+-- Roles
+-- Login Timestamp
+-- Device Registrations
+-- Other MetadataThat difference dramatically increases the impact of an authorization failure.
8. Recognizing the Repository Pattern
The API behavior was consistent with a Spring Data REST-style repository exposure.
This is an important pattern for bug bounty hunters to recognize.
Frameworks can automatically expose repository resources.
That is convenient for development, but it becomes dangerous when a sensitive repository is reachable through a public gateway route without explicit authorization.
The architecture can effectively become:
Database
|
v
Repository
|
v
Spring Data REST
|
v
Gateway
|
v
InternetDatabase
|
v
Repository
|
v
Spring Data REST
|
v
Gateway
|
v
InternetThe important question is therefore not:
"Is Spring Data REST vulnerable?"
It isn't necessarily.
The actual question is:
"Was a sensitive repository unintentionally made reachable without the authorization controls that should protect it?"
That distinction is important when writing accurate vulnerability reports.
9. Determining the Scale
Once the authentication bypass was confirmed, I wanted to understand the scope.
The endpoint returned the entire collection rather than requiring individual user identifiers.
The captured response contained approximately:
103,349 user records103,349 user recordsThe dataset contained approximately:
103,262 distinct institutional email addresses103,262 distinct institutional email addressesThe complete response was approximately:
116 MB116 MBand was streamed using chunked transfer encoding.
This transformed the finding from:
Unauthenticated access to user informationUnauthenticated access to user informationinto:
Unauthenticated mass disclosure of the entire user directoryUnauthenticated mass disclosure of the entire user directoryThat distinction is extremely important for severity assessment.
10. Device Registration Exposure
The dataset also contained device registration information.
Approximately:
28,55628,556push-notification device registrations were observed.
The records contained device types and token-like identifiers associated with push notification infrastructure, including Android/iOS/web registrations.
I did not attempt to use the tokens.
There was no need to.
The unauthorized exposure itself was sufficient to demonstrate the security impact.
This is an important principle when testing real-world applications:
Don't escalate an already-proven vulnerability simply to make the report more dramatic.
Once sensitive credentials or security-sensitive identifiers are exposed, additional exploitation may create unnecessary risk.
11. Privileged Staff Roles
Another interesting aspect of the dataset was the presence of organizational roles.
Examples included roles associated with:
Human Resources
Finance
Financial Assistance
RegistrationHuman Resources
Finance
Financial Assistance
RegistrationApproximately twenty distinct privileged/editor-style role categories were observed.
This adds another dimension to the vulnerability.
An attacker could potentially distinguish between:
General usersGeneral usersand:
Users associated with sensitive organizational functionsUsers associated with sensitive organizational functionsThat information can make targeted phishing and social-engineering campaigns substantially more convincing.
For example, instead of having only:
person@example.comperson@example.coman attacker could potentially have:
Name
+
Institutional Email
+
Organizational Role
+
Last LoginName
+
Institutional Email
+
Organizational Role
+
Last LoginThis creates a much more valuable reconnaissance dataset.
12. What I Did Not Do
Because the endpoint exposed real user information, I intentionally limited testing.
I did not:
- Attempt to log into other accounts.
- Attempt password resets.
- Attempt account takeover.
- Attempt to send push notifications.
- Attempt to use exposed device tokens.
- Modify user records.
- Delete or change data.
- Contact exposed users.
- Perform credential stuffing.
- Attempt destructive actions.
The objective was to demonstrate:
Unauthenticated access
+
Sensitive information
+
Mass extractionUnauthenticated access
+
Sensitive information
+
Mass extractionThat was already sufficient.
Responsible disclosure is especially important when a vulnerability involves a large population of real users.
13. Why the 400 Was the Most Important Clue
The most valuable lesson from this finding was understanding the difference between:
Routing validationRouting validationand:
AuthenticationAuthenticationThe application effectively behaved like:
Request
|
v
Tenant header present?
|
+---- NO ----> 400
|
YES
|
v
Continue processing
|
v
Authentication enforced?
|
+---- NO
|
v
200 OKRequest
|
v
Tenant header present?
|
+---- NO ----> 400
|
YES
|
v
Continue processing
|
v
Authentication enforced?
|
+---- NO
|
v
200 OKSo:
400400did not mean:
UnauthorizedUnauthorizedIt meant:
Unable to route the request.Unable to route the request.This is a useful mindset for API testing.
Whenever you encounter a 400, ask:
What validation failed?
Don't automatically interpret it as an authorization barrier.
14. Testing Authentication Properly
A useful approach is to build an authentication matrix.
For example:
No TenantValid TenantNo Authentication??Invalid Authentication??Valid Authentication??
Then record:
HTTP status
Response size
Response structure
Authentication headers
Sensitive fieldsHTTP status
Response size
Response structure
Authentication headers
Sensitive fieldsIn this case, the interesting state was:
No Authentication
+
Valid Tenant
=
Full DatasetNo Authentication
+
Valid Tenant
=
Full Datasetwhile an invalid bearer token produced essentially the same result.
That is strong evidence that authentication was not participating in the authorization decision.
15. General API Enumeration Methodology
This finding also demonstrates a repeatable workflow.
Step 1 — Map the application
Look for:
API endpoints
JavaScript bundles
API prefixes
Microservice names
Authentication endpoints
Tenant identifiers
Public configurationAPI endpoints
JavaScript bundles
API prefixes
Microservice names
Authentication endpoints
Tenant identifiers
Public configurationDon't limit reconnaissance to HTML pages.
Step 2 — Identify interesting namespaces
Pay attention to:
/api/
/admin/
/internal/
/open/
/public/
/guest/
/users/
/accounts/
/identity/
/profile//api/
/admin/
/internal/
/open/
/public/
/guest/
/users/
/accounts/
/identity/
/profile/These paths aren't automatically vulnerable.
They are simply areas where authorization mistakes can have significant impact.
Step 3 — Find collection endpoints
Search for endpoints such as:
/users
/accounts
/members
/students
/employees
/customers
/profiles/users
/accounts
/members
/students
/employees
/customers
/profilesA collection endpoint deserves additional attention because a single authorization failure may expose thousands of objects.
Step 4 — Remove authentication
If you have a legitimate authenticated session, reproduce the API request and then progressively remove:
Authorization
Cookies
Session identifiers
CSRF tokens
Custom authentication headersAuthorization
Cookies
Session identifiers
CSRF tokens
Custom authentication headersObserve what changes.
Step 5 — Test invalid authentication
Don't only test:
Valid tokenValid tokenAlso test:
No token
Invalid token
Expired token
Malformed tokenNo token
Invalid token
Expired token
Malformed tokenIf an invalid token receives the same sensitive response as a valid session, investigate further.
Step 6 — Identify routing requirements
If the API requires:
X-Tenant-ID
X-Organization-ID
X-Client-IDX-Tenant-ID
X-Organization-ID
X-Client-IDdetermine whether the value is:
SecretSecretor:
Public routing informationPublic routing informationA public tenant identifier is not equivalent to a password.
Step 7 — Inspect the response schema
Look for:
Internal IDs
Email addresses
Roles
Timestamps
Device identifiers
Tokens
Administrative metadata
References to other objectsInternal IDs
Email addresses
Roles
Timestamps
Device identifiers
Tokens
Administrative metadata
References to other objectsThe fields that matter most are often fields that aren't visible in the UI.
16. Don't Stop at the First Record
One common mistake in API testing is proving:
I can access a user.I can access a user.and immediately writing the report.
Before doing that, determine whether the API supports:
Single objectSingle objector:
Entire collectionEntire collectionFor example:
/users/123/users/123might expose one record.
But:
/users/userscould expose:
100
1,000
10,000
100,000+100
1,000
10,000
100,000+records.
The second scenario can have an entirely different severity.
17. Quantifying Impact
A strong report should quantify what can safely be quantified.
Instead of writing:
"A huge number of users are exposed."
provide measurable evidence:
~103,349 records
~103,262 unique institutional emails
~28,556 push registrations
~116 MB response~103,349 records
~103,262 unique institutional emails
~28,556 push registrations
~116 MB responseThis helps the security team understand the actual blast radius.
It also makes severity assessment much easier.
18. Why Mass Disclosure Is Different
Compare these two vulnerabilities:
Scenario A
Unauthenticated access to one user's profileUnauthenticated access to one user's profileversus:
Scenario B
Unauthenticated access to the entire user directoryUnauthenticated access to the entire user directoryThe underlying authorization mistake may be similar.
The impact is not.
The second vulnerability allows automated collection:
API
|
v
103,349 records
|
v
Attacker database
|
+-- Names
+-- Emails
+-- Roles
+-- Timestamps
+-- Device registrationsAPI
|
v
103,349 records
|
v
Attacker database
|
+-- Names
+-- Emails
+-- Roles
+-- Timestamps
+-- Device registrationsThis is why collection endpoints should always be evaluated for scale.
19. Potential Abuse
The exposed information could enable:
Targeted phishing
Real names combined with real institutional email addresses can make phishing campaigns significantly more convincing.
Staff targeting
Role information can help attackers identify employees associated with sensitive departments.
Organizational reconnaissance
Instead of discovering employees individually, an attacker receives a large pre-built directory.
Push infrastructure exposure
Device-registration information adds another security-sensitive category to the disclosure.
The important point is that these are potential consequences.
There is no need to actually perform these attacks to establish the vulnerability.
20. Root Cause
The apparent root cause was an authorization boundary failure involving the gateway's /open/ route.
Conceptually:
API Gateway
|
+----------+----------+
| |
Protected Routes /open/*
| |
Authentication Authentication
enforced? bypassed
| |
v v
Backend APIs Identity API
|
v
User Repository
|
v
Entire CollectionAPI Gateway
|
+----------+----------+
| |
Protected Routes /open/*
| |
Authentication Authentication
enforced? bypassed
| |
v v
Backend APIs Identity API
|
v
User Repository
|
v
Entire CollectionThe dangerous combination was:
Publicly reachable route
+
Authentication bypass
+
Raw identity repository
+
Collection endpointPublicly reachable route
+
Authentication bypass
+
Raw identity repository
+
Collection endpointThis turned what may have been intended as a public API namespace into a mass data-disclosure vulnerability.
21. Remediation
21.1 Enforce Authentication
The user collection endpoint should require authentication.
Authentication should be enforced independently of the tenant-routing mechanism.
21.2 Enforce Authorization
Authentication alone isn't enough.
The application should verify that the authenticated user is authorized to access the requested tenant and resource.
Conceptually:
Authenticate user
|
v
Determine tenant
|
v
Authorize requested resource
|
v
Return only permitted dataAuthenticate user
|
v
Determine tenant
|
v
Authorize requested resource
|
v
Return only permitted data21.3 Audit /open/ Routes
If the gateway currently treats:
/open/*/open/*as unauthenticated, every endpoint under that namespace should be reviewed.
Fixing only one endpoint may leave other sensitive APIs exposed.
21.4 Avoid Raw Repository Exposure
Sensitive persistence entities should not be automatically exposed to the Internet.
Prefer explicit DTOs:
class PublicUser:
first_name: str
last_name: strclass PublicUser:
first_name: str
last_name: strinstead of returning the complete database entity.
The API should explicitly control which fields are exposed.
21.5 Remove Sensitive Fields
Fields such as:
Internal database IDs
Secondary email
Device registrations
Push tokens
Internal role metadataInternal database IDs
Secondary email
Device registrations
Push tokens
Internal role metadatashould not be included in an unauthenticated directory response.
21.6 Implement Pagination and Limits
Even authenticated APIs should use:
Pagination
Maximum page size
Rate limiting
Abuse detectionPagination
Maximum page size
Rate limiting
Abuse detectionThis does not replace authorization, but it reduces the impact of accidental bulk exposure.
21.7 Rotate Exposed Device Registrations
Because push-registration identifiers were exposed, affected registrations should be evaluated and rotated or invalidated where appropriate.
22. Lessons for Bug Bounty Hunters
Lesson 1 — Status codes need context
A:
400 Bad Request400 Bad Requestdoes not automatically mean:
Authentication required.Authentication required.Find out why the server returned it.
Lesson 2 — Routing is not authorization
A tenant identifier can determine:
Which backend / tenant handles the requestWhich backend / tenant handles the requestwithout proving:
Who is allowed to access itWho is allowed to access itLesson 3 — Investigate special path prefixes
Paths like:
/open/
/public/
/guest//open/
/public/
/guest/should trigger questions.
Not assumptions.
Lesson 4 — Test APIs directly
The frontend may enforce restrictions that the backend does not.
Always investigate the underlying API request.
Lesson 5 — Look for collection endpoints
Whenever you discover:
/users/usersask:
Can I access it?
How many records?
Which fields?
Is pagination enforced?
Is authorization checked?Can I access it?
How many records?
Which fields?
Is pagination enforced?
Is authorization checked?Lesson 6 — Compare authentication states
A simple matrix can reveal broken authentication much faster than random payload testing.
Test:
No token
Invalid token
Valid tokenNo token
Invalid token
Valid tokenand compare the responses.
Lesson 7 — Inspect fields, not just status codes
A 200 OK isn't necessarily interesting.
A 200 OK containing:
Names
Emails
Roles
Tokens
Internal IDsNames
Emails
Roles
Tokens
Internal IDsis a completely different situation.
Lesson 8 — Measure the blast radius
Don't say:
"Lots of users."
Find out whether it is:
1 user
100 users
10,000 users
100,000 users1 user
100 users
10,000 users
100,000 usersQuantification makes the report substantially stronger.
Lesson 9 — Know when to stop
Once you have proven:
No authentication
+
Sensitive data
+
Mass extractionNo authentication
+
Sensitive data
+
Mass extractionadditional exploitation is usually unnecessary.
The best hunters understand not only how to exploit a vulnerability, but also how to stop responsibly.
23. The Complete Reasoning Chain
Looking back, the vulnerability can be represented as a simple chain:
API reconnaissance
|
v
Identity service discovered
|
v
/open/ namespace identified
|
v
/users collection discovered
|
v
400 response without tenant
|
v
Investigate reason for 400
|
v
Tenant header identified
|
v
Tenant value found to be publicly discoverable
|
v
Request repeated with tenant header
|
v
200 OK without authentication
|
v
Invalid bearer produces same result
|
v
Full collection returned
|
v
103,349 records identified
|
v
Sensitive fields identified
|
v
28,556 push registrations identified
|
v
Mass unauthenticated PII disclosureAPI reconnaissance
|
v
Identity service discovered
|
v
/open/ namespace identified
|
v
/users collection discovered
|
v
400 response without tenant
|
v
Investigate reason for 400
|
v
Tenant header identified
|
v
Tenant value found to be publicly discoverable
|
v
Request repeated with tenant header
|
v
200 OK without authentication
|
v
Invalid bearer produces same result
|
v
Full collection returned
|
v
103,349 records identified
|
v
Sensitive fields identified
|
v
28,556 push registrations identified
|
v
Mass unauthenticated PII disclosureThe vulnerability wasn't found through a complicated exploit.
It came from repeatedly asking:
Why?
Why does this return 400?
Why does the tenant header change the response?
Why does an invalid bearer token not matter?
Why does /open/ have access to a user repository?
Why is the entire collection returned?
Why are sensitive fields present?
That investigative mindset is often more valuable than any individual payload.
24. Final Takeaway
The most important lesson from this vulnerability is simple:
Don't assume that an endpoint is protected just because the application initially rejects your request.
A routing requirement is not authentication.
A public tenant identifier is not a credential.
An /open/ prefix is not proof that the endpoint is safe.
And a collection endpoint backed by a raw identity repository can turn a small authorization mistake into a massive privacy breach.
The final vulnerability chain was:
Public tenant identifier
↓
/open/ gateway route
↓
Authentication bypass
↓
Identity repository exposed
↓
Raw user entities returned
↓
Entire collection accessible
↓
103K+ user records
↓
103K+ institutional email addresses
↓
28K+ push registrations
↓
Mass unauthenticated PII disclosurePublic tenant identifier
↓
/open/ gateway route
↓
Authentication bypass
↓
Identity repository exposed
↓
Raw user entities returned
↓
Entire collection accessible
↓
103K+ user records
↓
103K+ institutional email addresses
↓
28K+ push registrations
↓
Mass unauthenticated PII disclosureFor bug bounty hunters, the biggest takeaway is:
Don't just test whether an endpoint works. Understand why it works, what security boundary is supposed to protect it, and what happens when that boundary is removed.
That mindset is what turns ordinary API reconnaissance into meaningful vulnerability discovery.