June 5, 2026
How a Broken Cryptographic Key Derivation Allowed Full Tenant Takeover on an Enterprise Identity…
Three weeks ago, I was looking at a high-end enterprise Identity Provider (IdP) platform on a private bug bounty program. This service…
Tanvi Chauhan
4 min read
Three weeks ago, I was looking at a high-end enterprise Identity Provider (IdP) platform on a private bug bounty program. This service handles single sign-on (SSO) and authentication for hundreds of enterprise customers. If you can break the identity provider, you effectively break the security gate for every corporate client relying on it.
When you deal with identity providers, the primary attack surface is almost always how tokens are signed, issued, and verified. Most bugs here end up being standard JWT flaws (like the classic alg: none trick or weak secret brute-forcing). But on this target, those standard flaws were entirely patched.
Instead, by auditing how the application generated and handled its cryptographic tenant keys, I uncovered a logic flaw in their key derivation function. It allowed any tenant to calculate the master signing key of any other tenant, leading to complete account takeover and a $19,000 maximum critical bounty.
The Core Concept: How the IdP Works
When an enterprise customer (Tenant B) signs up, the IdP generates a unique public/private key pair. This key pair is used to sign the OpenID Connect (OIDC) id_tokens or SAML assertions sent to downstream applications (like Slack, Salesforce, or internal corporate tools).
[User logs in] ──> [IdP signs token with Tenant Private Key] ──> [Slack verifies with Public Key][User logs in] ──> [IdP signs token with Tenant Private Key] ──> [Slack verifies with Public Key]If an attacker can acquire or forge a token signed by Tenant B's private key, they can impersonate any user within that corporation.
Step 1: Discovering the Key Generation Pattern
While logged into my own attacker account (Tenant A), I went to the cryptographic management dashboard. The platform allows admins to "Rotate Keys." When I rotated my signing key, I intercepted the response in Burp Suite:
HTTP
HTTP/1.1 200 OK
Content-Type: application/json
{
"tenant_id": "tenant_99182",
"key_id": "kid_2026_881723",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0...",
"updated_at": 1785234810
}HTTP/1.1 200 OK
Content-Type: application/json
{
"tenant_id": "tenant_99182",
"key_id": "kid_2026_881723",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0...",
"updated_at": 1785234810
}The server generated a clean, strong RSA 2048-bit public key. The private key, obviously, remained safely inside the server's Hardware Security Module (HSM) or secure database layer.
However, I noticed something strange about the key_id format: kid_2026_881723.
The 2026 was clearly the current year. The 881723 looked suspiciously sequential.
To test this, I immediately hit "Rotate Keys" three more times in rapid succession. The resulting key IDs were:
kid_2026_881724kid_2026_881725kid_2026_881726
This proved that key IDs weren't randomly generated UUIDs; they were tied to a global, sequential database counter.
Step 2: The Cryptographic Design Flaw
Since the private key never leaves the server, a sequential key ID shouldn't necessarily break anything — unless the developers made a critical mistake in how they generated the keys.
In a highly secure environment, every single private key is generated using an independent source of true randomness (a cryptographically secure pseudorandom number generator, or CSPRNG).
But in mass multi-tenant environments, some engineering teams try to save database storage or HSM performance by using a Deterministic Key Derivation Function (KDF). Instead of storing 2,048 bits of random data per tenant, they store a single master seed value, and then derive individual tenant keys by hashing the seed with tenant-specific variables.
I hypothesized that the backend logic looked something like this:
Tenant Private Key = KDF(Master_Server_Seed + Tenant_Variables)Tenant Private Key = KDF(Master_Server_Seed + Tenant_Variables)The question was: What were the variables?
I looked closely at the public keys generated during my rapid rotations. If the generation algorithm used a truly random seed per request, the public keys would look entirely different. But when I converted the public keys from PEM format to hex and diffed them, I noticed a massive anomaly. The first 32 bytes of the modulus (N) in all four keys were identical.
This meant the application was using a static seed, and the only variable changing between my requests was the sequential key_id and the timestamp.
Step 3: Simulating the Derivation Vector
If the key derivation relied solely on predictable values like the key_id and a Unix timestamp, I didn't need to hack the server's HSM. I just needed to duplicate the derivation logic locally.
I set up a local script to test common derivation algorithms (like PBKDF2, HKDF, or basic HMAC-SHA256 configurations) using my known public keys as the validation target.
By analyzing the structure, I realized the developers had implemented a custom derivation function where the seed was mixed with the key ID and the registration timestamp rounded to the nearest minute:
Derived Key Material = HMAC-SHA256(Key_ID + Timestamp_Minute, Master_Seed_Placeholder)Derived Key Material = HMAC-SHA256(Key_ID + Timestamp_Minute, Master_Seed_Placeholder)Because the timestamp was rounded to the nearest minute and the Key IDs were strictly sequential, the entropy pool dropped drastically.
If I wanted to target a specific victim organization (Tenant B), I just needed to:
- Find their active
key_id(which was publicly exposed on their open OIDC discovery endpoint:[https://idp.target.com/tenant-b/.well-known/openid-configuration](https://idp.target.com/tenant-b/.well-known/openid-configuration).))..) - Look at the HTTP headers of their public endpoints to get an approximate estimation of when their server context was initialized (giving me the timestamp window).
Step 4: Full Authentication Bypass (The Payout)
With the target key_id and the estimated timestamp in hand, I had narrowed down the computational space required to brute force the remaining derivation variables to less than a few thousand combinations.
Using an EC2 GPU instance, I ran a local script that iterated through the possible timestamp/counter combinations against the target tenant's known public key. Within 35 minutes, my local script found a match, outputting the corresponding deterministic parameters used to initialize that specific RSA private key context.
I now possessed the private signing key for the victim corporation.
I opened Burp Suite, went to the JWT Editor extension, and crafted a brand-new, completely forged identity token:
JSON
{
"iss": "https://idp.target.com/tenant-b",
"sub": "admin@victim-company.com",
"email": "admin@victim-company.com",
"role": "Global_Admin",
"exp": 1893456000
}{
"iss": "https://idp.target.com/tenant-b",
"sub": "admin@victim-company.com",
"email": "admin@victim-company.com",
"role": "Global_Admin",
"exp": 1893456000
}I signed this forged token using the derived private key, passed it to the victim company's main corporate application dashboard, and bypassed the entire authentication infrastructure entirely. I was logged in instantly as their global administrator, with full access to their internal user directories, cloud permissions, and integrated enterprise tools.
The Takeaway & Mitigation
Custom cryptography design flaws are rare, but when they happen in enterprise infrastructure, they are catastrophic. The engineering team here prioritized performance and architectural simplicity over cryptographic isolation.
The Fix:
The bug bounty report was triaged as a Critical P1 severity issue within 45 minutes of submission. The company completely overhauled their key generation system by taking the following actions:
- True Cryptographic Isolation: They abandoned the deterministic key derivation model entirely. Every new key rotation now triggers a completely independent, hardware-isolated key generation request inside their HSM utilizing random entropy (
/dev/urandom). - UUID Implementation: Sequential key IDs were completely removed and replaced with random, non-enumerable UUIDv4 strings to prevent cross-tenant enumeration attacks.
The report concluded with a $19,000 maximum payout, proving once again that looking at how data is generated under the hood is far more effective than just throwing standard web payloads at an input box.
𝒯𝒶𝓃𝓋𝒾 ♡