# Poweradmin: OIDC `sub` collation bypass in Poweradmin leading to account takeover

Poweradmin's OIDC link table used accent-insensitive utf8mb4_unicode_ci, so an attacker's `sub` like `victím-login` matched the victim's `victim-login` row and yielded the victim's session.

- Payout: Research
- Severity: high
- Program: Poweradmin
- Found by: William957-web
- Disclosed: 2026-07-24
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-cmwh-g2h8-c222
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-cmwh-g2h8-c222
- Tags: area:web domain:web impact:ato kind:research platform:ghsa sev:high tech:oidc tech:php technique:source-review technique:unicode-normalization vuln:account-takeover vuln:auth-bypass

**Target:** OIDC login/callback endpoint backed by MySQL oidc_user_links table
**Root cause:** The `oidc_user_links.oidc_subject` column uses `utf8mb4_unicode_ci`, an accent- and case-insensitive collation, and the provisioning lookup uses plain SQL equality, so distinct OIDC subjects compare equal.
**Steps:**
1. Create two OIDC provider accounts: victim `victim-login` and attacker `victím-login` (í = U+00ED).
2. Victim logs in via `GET /oidc/login?provider=generic`; Poweradmin creates local user and row `oidc_user_links(user_id=2, provider_id='generic', oidc_subject='victim-login')`.
3. Attacker logs in via the same flow with their own password `AttackerPassword123!`.
4. `UserProvisioningService::findUserByOidcSubject` runs `SELECT user_id FROM oidc_user_links WHERE oidc_subject = ? AND provider_id = ?` with `victím-login`; MySQL matches the victim row.
5. Poweradmin sets session `userlogin`/`userid`/`authenticated` from the returned victim user_id, giving the attacker the victim's session.
**How found:** Noticed the OIDC link table was declared `utf8mb4_unicode_ci` while OIDC `sub` is a byte-exact identifier; tested `SELECT 'victim-login' = 'victím-login' COLLATE utf8mb4_unicode_ci` returning 1, then traced the subject through getUserInfo into the equality lookup and session assignment.
**Exploitation:** Attacker registers an OIDC principal whose `sub` is an accent variant of the victim's subject, authenticates with their own IdP credentials, and Poweradmin resolves the session to the victim's local account without knowing the victim's password.
**Insight:** When an external identity claim (OIDC `sub`, SAML NameID) is stored in a DB column, check the column collation — case/accent-insensitive collations silently collapse distinct identities at the SQL equality layer even if app code compares strings correctly.

## Proof of concept (copied exactly from the original report)

**SQL collation equality check proving accent collision**

```
SELECT 'victim-login' = 'victím-login' COLLATE utf8mb4_unicode_ci;
-- accent_collision = 1
```

**Vulnerable subject lookup query**

```
$stmt = $this->db->prepare("
    SELECT user_id FROM oidc_user_links
    WHERE oidc_subject = ? AND provider_id = ?
");
$stmt->execute([$subject, $providerId]);
```

**PoC harness output showing attacker resolved to victim**

```
{
  "attacker_login": {
    "selected_idp_user": "attacker",
    "selected_idp_username": "victím-login",
    "has_session_cookie": true,
    "home_contains_victim_username": true,
    "home_contains_attacker_username": false
  },
  "attacker_resolved_to_victim": true
}
```

