Pentest Files: How A Single HTTP Header Unlocked Every Customer’s Data

A single HTTP header. Fully client-controlled. Trusted completely by the server. In this Pentest Files, Daniel shows how modifying one value in a routine API request was enough to pull user data from every organisation on a multi-tenant SaaS platform, no special privileges required, no complex exploit chain, just a for loop and an integer.

Welcome to our Pentest Files series. Each post presents a real, interesting, or dangerous finding one of our testers identified during an actual engagement, so you can see the kinds of things our pentesters get up to and take steps to prevent similar vulnerabilities in your own assets. Findings are taken from real reports and anonymised to protect client confidentiality.

The Finding

During a recent web application penetration test, our tester, Daniel Coupland, uncovered a high broken access control vulnerability in a multi-tenant SaaS platform. What made this one particularly striking wasn’t complexity – it was simplicity. A single HTTP header, trusted blindly by the server, gave any authenticated user unrestricted access to every other organisation’s data on the platform.

Vulnerability type: Broken Cross-Tenant Access Controls (IDOR / Broken Object-Level Authorisation)

Severity: High

Discovered by: Daniel Coupland, Penetration Tester at OnSecurity

The Setup: How It Started

Daniel was testing a web application built for multiple organisations, the classic multi-tenant SaaS model, where each customer logs in to their own workspace, sees only their own data, and manages their own users. Tenant isolation is the foundational security guarantee of this architecture. Tenant A must never see Tenant B’s data. That’s the contract.

While working through the application’s API calls in Burp Suite, Daniel noticed something in the request headers. Alongside the Authorization header carrying a valid bearer token, every API request also included a custom header:

Tenantid: 3

In a well-built multi-tenant system, tenant context is derived server-side – from the authenticated session, or from claims embedded in the JWT token itself. The server should already know which organisation you belong to. It shouldn’t need to ask you.

This header was doing exactly that: asking the client to declare its own identity. Daniel’s next move was obvious.

The Exploit

Step 1 — Intercept a legitimate request

Daniel captured a standard API call to list users within his test organisation:

POST /api/v1/Users/all_users HTTP/2
Host: api.example-platform.com
Content-Type: application/json
Tenantid: 3
Authorization: Bearer [daniel's valid JWT]

[{"property":"limit","value":3},{"property":"orderBy","value":"displayName"},
{"property":"dateRange","operation":"allTime","value":[]},
{"property":"status","value":[2,1,4]}]

This returned his own tenant’s user list – working exactly as intended.

Step 2 — Modify the header

Daniel changed a single value: Tenantid: 3 became Tenantid: 2. His authentication token stayed exactly as it was. He replayed the request.

POST /api/v1/Users/all_users HTTP/2
Host: api.example-platform.com
Content-Type: application/json
Tenantid: 2
Authorization: Bearer [daniel's valid JWT]

Step 3 — The server obliges

The server responded with a 200 OK and returned the full user list for a completely different organisation – names, email addresses, roles, team assignments, and account metadata. Daniel had not authenticated as that organisation. He had not been granted access to it. He had simply asked for it, and the server handed it over.

Step 4 — Enumerate the rest

Here’s where a second design flaw compounded the first. Tenant IDs were sequential integers: 1, 2, 3, 4, and so on. No UUIDs. No random identifiers. Nothing prevents an attacker from simply counting upwards.

With a basic automated script – or even Burp Suite’s Intruder – Daniel could iterate through every tenant ID on the platform and retrieve data from every single organisation. One valid login. Every customer’s data.

Step 5 — Admin escalation across tenants

For admin-level accounts, the impact went further. Admin users could not only read other tenants’ data, but they could also perform privileged write actions against them, including adding themselves to other organisations as members. An attacker with an admin account could quietly insert themselves into another tenant, gaining persistent access that might never be noticed.

Why This Is Dangerous

This is one of the most impactful classes of vulnerability in multi-tenant SaaS applications. The damage isn’t theoretical:

  • Mass data breach across all customers. A single malicious or compromised user account could harvest user data, organisational details, and PII from every tenant on the platform — without triggering any unusual authentication alerts.
  • Regulatory and compliance exposure. Unauthorised access to personal data belonging to other organisations is a direct GDPR breach, potentially triggering mandatory notification, regulatory investigation, and significant fines.
  • Privilege escalation. Admin users could add themselves to other organisations, gaining persistent footholds that could be used to read sensitive records, tamper with data, or move further into the platform.
  • Trivial enumeration. Sequential integer IDs meant the attack required no guesswork. Every tenant on the platform was discoverable and accessible in a single automated sweep.
  • Silent and undetectable. Because the attacker uses their own valid credentials, there are no failed authentication events and no anomalous login alerts. From a logging perspective, it can look like entirely normal API activity.

The Remediation

The good news is that this class of vulnerability is entirely preventable. The client was provided with clear remediation guidance and, using OnSecurity’s platform, was able to validate the fixes through a free retest – confirming a clean 403 Forbidden response when attempting cross-tenant access.

Never trust client-supplied tenant identifiers. Derive tenant context entirely server-side, from the authenticated session or from claims embedded within the JWT itself. The TenantId should never be a value the client can set or override.

// Instead of reading from the header:
var tenantId = request.Headers["TenantId"];

// Read from the validated JWT claims:
var tenantId = user.Claims.FirstOrDefault(c => c.Type == "tenant_id")?.Value;

Enforce tenant membership validation on every request. Every API endpoint that returns or modifies tenant-scoped data should explicitly verify the authenticated user belongs to the requested tenant before processing. Centralise this check in middleware or a base controller – rather than duplicating it per endpoint, where it will inevitably be missed.

Replace sequential IDs with non-guessable identifiers. Tenant identifiers should be UUIDs or other cryptographically random values. Even with correct access control logic in place, sequential integers create a trivial enumeration surface that amplifies the impact of any future bypass.

Add automated cross-tenant testing. Include tenant isolation checks in your integration test suite. Create two test tenants, authenticate as a user in Tenant A, and assert that requests for Tenant B’s resources return 403 Forbidden. Run these on every build.

What Finding This Early Meant

An attacker who discovered this independently – a disgruntled competitor, a curious customer, a financially motivated threat actor – would have had unrestricted read access to every user record on the platform. No special skills required. No privilege escalation needed. Just a header and a for loop.

Daniel found it during a structured, time-boxed penetration test, reported it in real time through OnSecurity’s platform, and the client had a fix deployed and verified within days. That’s the difference between a finding in a report and a finding in the news.

If your application operates a multi-tenant model, it’s worth asking: does your server actually verify which organisation a user belongs to on every request, or does it just take their word for it?

Want to know if your application is vulnerable? Get a quote from OnSecurity.

Related Articles

The OnSecurity platform is currently experiencing issues. Our team is actively working to resolve this. Please try again shortly.