February 12, 2025
A real-world penetration testing case study showing how weak JWT validation, missing email verification, and subdomain misconfigurations led to full organizational compromise.
Ahmed M. Arafat
During a penetration testing engagement for a web application of one of our clients, I identified an unusual behavior in the application's signup process which allowed for registration with the same email twice for the same API. Further investigation revealed a critical vulnerability: the JWT validation was based on email addresses, not on unique identifiers, and there was no email verification process. Additionally, the same API was used across various subdomains, which meant the compromised JWT could be exploited to access other users' accounts on these subdomains. Leveraging this, we successfully executed an account takeover and eventually escalated it to an organization-wide takeover, enabling us to control any user account without their interaction.
The target, a B2B service provider, hosts client services under their subdomains, such as B-one.target.com
and B-two.target.com
. Both subdomains offer signup options as they provide distinct services. Interestingly, both B-One and B-Two are powered by the same API: api.target.com
. In an experiment, I successfully signed up on both B-one and B-two using the same email, despite them ostensibly using the same database and code base through this shared API. A closer inspection revealed a crucial detail: a header named Source in the request. This header serves to identify the source of the request, whether it's from B-One (Source: B-One) or B-Two (Source: B-Two), presumably to prevent data or request conflicts between the two subdomains.
Missing Email Confirmation: Upon registering on B-one.target.com
using the email [email protected]
, I observed that there was no email confirmation process implemented.
JWT Validation Based on Email Address: During my exploration of the user information update feature, I noticed a peculiar behavior. Changing the email address resulted in the application logging me out. This led me to deduce that JWT validation is conducted based on the email address. This hypothesis is further supported by the structure of the JWT payload, which includes both the email and user ID. The payload appears as follows:
The account for the user [email protected]
was successfully created on B-one.target.com
. Here’s the signup request for this account:
POST /users/sign-up HTTP/2
Host: api.target.com
Source: B-One
{
"email": "[email protected]",
"password": "P@ssWord",
"FirstName": "Target",
"LastName": "User"
}
The request's Source Header, set to B-One, assists the server in identifying the source as B-one.target.com
. The response to this includes the user’s JWT.
Next, as an attacker, I proceeded to B-two.target.com
and signed up with the same email, [email protected]
. The request looked like this:
POST /users/sign-up HTTP/2
Host: api.target.com
Source: B-Two
{
"email": "[email protected]",
"password": "P@ssWord",
"FirstName": "Attacker",
"LastName": "User"
}
Since no account with [email protected]
existed under the B-Two channel, a new account was created, returning a JWT with the same email but a different ID:
{
"iat": 1704719526,
"exp": 1704725026,
"username": "[email protected]",
"id": "960920"
}
To fetch user data, the following request is used:
GET /user/me HTTP/2
Host: api.target.com
Authorization: JWT {Attacker's JWT}
Source: B-Two
The server validates the email in the JWT against the specified channel. In this case, it would return the attacker's data since, on B-Two, the email corresponds to the attacker's account. However, changing the channel to B-One:
GET /user/me HTTP/2
Host: api.target.com
Authorization: JWT {Attacker's JWT}
Source: B-One
This request leads to accessing the target user's data on B-One using the attacker's JWT From B-Two, thereby exploiting the vulnerability to achieve account takeover.
After documenting my findings, I passed them over to my teammate Khaled Hassan. With diligent reconnaissance, Khaled successfully located an administrator's email associated with one of the target channels or clients. Leveraging the same vulnerabilities we had previously identified, we executed a strategy for an admin account takeover. This successful breach allowed us to extend our control beyond individual user accounts, culminating in a complete organization takeover. This critical escalation highlighted the extensive impact of the identified security weaknesses in the web application's architecture.
This case study serves as a stark reminder of how seemingly minor misconfigurations, coupled with atypical application behaviors, can escalate into a severe security breach, culminating in a full organizational account takeover. The journey began with observing the dual registration possibility under different subdomains with the same email, an anomaly that hinted at underlying vulnerabilities. This was further compounded by the discovery of JWT validation being solely email-based, without any email verification process, and the critical role of the Source header in segregating user data by subdomains.
The breakthrough came when these individual elements were combined. By exploiting the lack of email verification and manipulating the JWT token in conjunction with the Source header, it became possible to crossover user identities between subdomains. This allowed for an account takeover on one subdomain to be leveraged into unauthorized access on another.
Finally, this vulnerability was taken to its ultimate conclusion by my colleague Khaled Hassan, who, through effective reconnaissance, identified an administrator's email, thereby paving the way for an organization-wide takeover.