criticalCVE-2026-49757Aug 25, 2026

CVE-2026-49757: ash_authentication OAuth2/OIDC Account Takeover via Email Spoofing

Rohit Hatagale
AI Security Researcher, SecureLayer7

A flaw in how AshAuthentication matched OAuth2 and OIDC logins to local users let an attacker sign in with a victim's email on any accepted OAuth provider and immediately take over the victim's local…

Packageash_authentication
Ecosystemerlang
Affected>= 0.1.0, < 4.14.0
Fixed in4.14.0
CVE-2026-49757: ash_authentication OAuth2/OIDC Account Takeover via Email Spoofing

The problem

AshAuthentication's OAuth2 and OIDC strategies used email as the upsert identity and sign-in filter instead of the OpenID Connect iss/sub claim pair. Per OIDC Core Section 5.7, only iss/sub uniquely identifies a user across providers; email is mutable, reusable, and often unverified.

Any attacker who could present the victim's email via an OAuth provider, whether through a provider that skips email verification, email reclamation after account deletion, or organisation off-boarding, would be resolved to the victim's existing local record. No application misconfiguration was required; the default generated actions were vulnerable from the start.

Proof of concept

A working proof-of-concept for CVE-2026-49757 in ash_authentication, with the exact payload below.

http
# Attacker registers on any accepted OAuth/OIDC provider using victim@example.com.
# Provider does NOT require verified email ownership (Slack, generic OIDC, custom OAuth2, etc.).
# Attacker initiates the standard OAuth2 authorization code flow:

GET /auth/oauth2/example/callback?code=ATTACKER_AUTH_CODE&state=VALID_STATE HTTP/1.1
Host: target-app.example.com

# Inside ash_authentication (<4.14.0), the register action runs:
#
#   create :register_with_oauth2 do
#     upsert? true
#     upsert_identity :email          # <-- resolves on email, not iss/sub
#     change AshAuthentication.Strategy.OAuth2.IdentityChange
#     change AshAuthentication.GenerateTokenChange
#   end
#
# IdentityChange sets changeset email = user_info["email"] = "victim@example.com".
# The upsert lands on the VICTIM'S existing row.
#
# SignInPreparation then runs:
#
#   read :sign_in_with_oauth2 do
#     prepare AshAuthentication.Strategy.OAuth2.SignInPreparation
#     filter expr(email == get_path(^arg(:user_info), [:email]))
#   end
#
# Filter matches victim's record. No iss/sub check. Attacker receives victim's session token.

# Response: 200 OK with a valid AshAuthentication JWT/session for victim@example.com

The root cause (CWE-290, Authentication Bypass by Spoofing) is that the upsert identity key was :email, so the Ash upsert action merged the attacker's OAuth profile into the victim's database row. The post-upsert sign-in preparation filtered by email alone with no cross-check against a stored iss/sub identity, so the attacker was returned as the authenticated user.

The patch (commit 728b8d28 for 4.x, 64530644 for 5.x) changes the lookup to match on the (strategy name, provider sub) tuple stored in the user identity resource. Email-based account linking is now gated behind the trust_email_verified? option, which requires email_verified: true in the provider's token claims before linking a new sub to an existing email.

The fix

Upgrade ash_authentication to 4.14.0 (4.x line) or 5.0.0-rc.10 (5.x line). After upgrading, review any custom sign-in actions that still use filter expr(email == ...) and replace them with iss/sub-based lookups. If you intentionally want email-based account linking, enable it explicitly with trust_email_verified?: true and only use providers that reliably set email_verified: true in their token claims.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research