GHSA-cmwh-g2h8-c222 Poweradmin: OIDC `sub` collation bypass in Poweradmin leading to account takeover
PrefacePoweradmin maps OIDC identities into local users through `oidc_user_links.oidc_subject` plus `provider_id`. In the MySQL schema, the OIDC link table explicitly uses `utf8mb4_unicode_ci`, which is case-insensitive and accent-insensitive. OIDC `sub` is a stable external subject identifier and should be matched byte-for-byte within the issuer/provider scope.The confirmed local PoC used two different OIDC users:Victim subject: `victim-login`Attacker subject: `victím-login` (`í`, U+00ED)MySQL reported those two subjects as equal under `utf8mb4_unicode_ci`. After the victim linked their OIDC account, the attacker authenticated to the same provider with the attacker's own password and Poweradmin resolved the session to the victim's local account.Server Info**Application:** Poweradmin**Version:** `targets/poweradmin` git `e1f9c9a`**Database:** MySQL `8.4.10`, `character_set_server=utf8mb4`, `collation_server=utf8mb4_unicode_ci`**Access Permissions:** Any user who can create or control an account in the connected OIDC provider**Auth Method:** OIDC generic provider**Tools:** Docker Compose, local OIDC provider, Python PoC harness**Affected Entry Point:**Relevant request properties:Authentication: valid OIDC authorization code flowTrigger: attacker OIDC account has a `sub` that collides with a victim's linked `sub`Vulnerable field: OIDC `sub` stored and looked up as `oidc_user_links.oidc_subject`Root Cause Analysispart0 — `oidc_user_links.oidc_subject` uses an accent-insensitive collationThe MySQL schema defines the OIDC link table with `utf8mb4_unicode_ci`:In the PoC database:part1 — OIDC `sub` flows into a normal SQL equality lookupPoweradmin reads the subject from OIDC userinfo data. If no custom `subject` mapping is configured, it uses the `sub` claim:The callback passes the resulting `OidcUserInfo` to provisioning:Provisioning first tries to find an existing user by subject:The lookup is normal SQL equality evaluated under the column's weak collation:When the attacker authenticates with `sub = victím-login`, MySQL matches the existing row for `oidc_subject = victim-login` and returns the victim's `user_id`.part2 — The returned user ID becomes the authenticated sessionAfter provisioning returns the matched `user_id`, Poweradmin fetches the database username for that user and stores the matched user ID in the session:The attacker's OIDC password is validated by the IdP, but the local `user_id` selected by Poweradmin comes from the weak-collation SQL lookup.Security ImpactAn attacker who can register or control an OIDC principal with an accent/collation variant of a victim's OIDC subject can authenticate with the attacker's own IdP credentials and obtain a Poweradmin session for the victim's local account.The confirmed PoC used distinct OIDC usernames, subjects, emails, and passwords. The attacker did not know or modify the victim's password.Reproduction1. Start the local Poweradmin OIDC labThe lab uses MySQL `utf8mb4_unicode_ci` and a local OIDC provider with two real login accounts:2. Log in once as the victim through OIDCAuthenticate through `GET /oidc/login?provider=generic` using:Poweradmin creates the victim local user and OIDC link:3. Log in as the attacker OIDC userAuthenticate from a separate browser session with:Observed result from `poc/work/poweradmin-oidc-collation/run_poc.py`:Database evidence after both logins:The application audit log also records all OIDC login events as the victim user:No local user or OIDC link was created for `victím-login`.Recommended FixTreat OIDC subject identifiers as byte-exact strings.For MySQL, migrate the OIDC mapping identifiers to a binary or byte-preserving collation:Also make lookup queries byte-preserving so patched application code protects existing deployments before schema migrations are complete:Review related identity and authorization lookups:`findUserByEmail()` uses `users.email = ?` and can be impacted when `link_by_email` is enabled.`findPermissionTemplateByName()` uses `perm_templ.name = ?` for SSO permission template mapping.`findGroupByName()` uses `user_groups.name = ?` for SSO group mapping.Those fields should either be intentionally documented as case/accent-insensitive or migrated/looked up with byte-preserving semantics where they represent security boundaries.PatchesFixed in 4.2.5, 4.3.4, and 4.4.0. OIDC and SAML subject identifiers are now matched byte-for-byte. The fix includes a database migration that changes the collation of the identity link columns, so upgrading requires running the SQL update script for your database in the `sql/` directory.Acknowledge / Creditwhale120 (@whale120_tw), working with DEVCORE Internship Program
CVSS v3.1 score: 8.1