PT-2026-67596 · Packagist · Poweradmin/Poweradmin
Published
2026-07-24
·
Updated
2026-07-24
CVSS v3.1
8.1
High
| Vector | AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
Preface
Poweradmin 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/poweradmingite1f9c9a - 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:
text
GET /oidc/login?provider=generic
GET /oidc/callback?code=...&state=...Relevant request properties:
- Authentication: valid OIDC authorization code flow
- Trigger: attacker OIDC account has a
subthat collides with a victim's linkedsub - Vulnerable field: OIDC
substored and looked up asoidc user links.oidc subject
Root Cause Analysis
part0 — oidc user links.oidc subject uses an accent-insensitive collation
The MySQL schema defines the OIDC link table with
utf8mb4 unicode ci:sql
-- sql/poweradmin-mysql-db-structure.sql:341-356
CREATE TABLE `oidc user links` (
`user id` INT(11) NOT NULL,
`provider id` VARCHAR(50) NOT NULL,
`oidc subject` VARCHAR(255) NOT NULL,
...
UNIQUE KEY `unique subject provider` (`oidc subject`, `provider id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4 unicode ci;In the PoC database:
text
Field Type Collation
provider id varchar(50) utf8mb4 unicode ci
oidc subject varchar(255) utf8mb4 unicode ci
SELECT 'victim-login' = 'victím-login' COLLATE utf8mb4 unicode ci;
-- accent collision = 1part1 — OIDC sub flows into a normal SQL equality lookup
Poweradmin reads the subject from OIDC userinfo data. If no custom
subject mapping is configured, it uses the sub claim:php
// lib/Application/Service/OidcService.php:424-459
$resourceOwner = $provider->getResourceOwner($token);
$userData = $resourceOwner->toArray();
...
subject: $userData[$mapping['subject'] ?? 'sub'] ?? '',The callback passes the resulting
OidcUserInfo to provisioning:php
// lib/Application/Service/OidcService.php:269-291
$userInfo = $this->getUserInfo($provider, $token, $providerId);
$userId = $this->userProvisioningService->provisionUser($userInfo, $providerId);Provisioning first tries to find an existing user by subject:
php
// lib/Application/Service/UserProvisioningService.php:86-90
$existingUserId = $authMethod === self::AUTH METHOD SAML
? $this->findUserBySamlSubject($userInfo->getSubject(), $providerId)
: $this->findUserByOidcSubject($userInfo->getSubject(), $providerId);The lookup is normal SQL equality evaluated under the column's weak collation:
php
// lib/Application/Service/UserProvisioningService.php:145-149
$stmt = $this->db->prepare("
SELECT user id FROM oidc user links
WHERE oidc subject = ? AND provider id = ?
");
$stmt->execute([$subject, $providerId]);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 session
After provisioning returns the matched
user id, Poweradmin fetches the database username for that user and stores the matched user ID in the session:php
// lib/Application/Service/OidcService.php:307-317
$databaseUsername = $this->userProvisioningService->getDatabaseUsername($userId);
$this->setSessionValue('userlogin', $databaseUsername);php
// lib/Application/Service/OidcService.php:360-371
$this->setSessionValue('userid', $userId);
...
$this->setSessionValue('authenticated', true);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 Impact
An 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.
Reproduction
1. Start the local Poweradmin OIDC lab
bash
sudo -n docker compose -p poweradminoidcpoc -f poc/work/poweradmin-oidc-collation/docker-compose.yml up -dThe lab uses MySQL
utf8mb4 unicode ci and a local OIDC provider with two real login accounts:text
Victim:
username/sub: victim-login
email: victim.poweradmin@example.com
password: VictimPassword123!
Attacker:
username/sub: victím-login
email: attacker.poweradmin@example.com
password: AttackerPassword123!2. Log in once as the victim through OIDC
Authenticate through
GET /oidc/login?provider=generic using:text
username: victim-login
password: VictimPassword123!Poweradmin creates the victim local user and OIDC link:
text
users:
id username fullname email
2 victim-login Victim Poweradmin victim.poweradmin@example.com
oidc user links:
id user id provider id oidc subject
1 2 generic victim-login3. Log in as the attacker OIDC user
Authenticate from a separate browser session with:
text
username: victím-login
password: AttackerPassword123!Observed result from
poc/work/poweradmin-oidc-collation/run poc.py:json
{
"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
}Database evidence after both logins:
text
version charset server collation server
8.4.10 utf8mb4 utf8mb4 unicode ci
accent collision
1
users:
id username fullname email
2 victim-login Victim Poweradmin victim.poweradmin@example.com
oidc user links:
id user id provider id oidc subject oidc subject hex
1 2 generic victim-login 76696374696D2D6C6F67696EThe application audit log also records all OIDC login events as the victim user:
text
user:victim-login operation:login success auth method:oidcNo local user or OIDC link was created for
victím-login.Recommended Fix
Treat OIDC subject identifiers as byte-exact strings.
For MySQL, migrate the OIDC mapping identifiers to a binary or byte-preserving collation:
sql
ALTER TABLE oidc user links
MODIFY COLUMN provider id varchar(50)
CHARACTER SET utf8mb4 COLLATE utf8mb4 bin NOT NULL,
MODIFY COLUMN oidc subject varchar(255)
CHARACTER SET utf8mb4 COLLATE utf8mb4 bin NOT NULL;Also make lookup queries byte-preserving so patched application code protects existing deployments before schema migrations are complete:
php
$stmt = $this->db->prepare("
SELECT user id FROM oidc user links
WHERE BINARY oidc subject = BINARY ?
AND BINARY provider id = BINARY ?
");Review related identity and authorization lookups:
findUserByEmail()usesusers.email = ?and can be impacted whenlink by emailis enabled.findPermissionTemplateByName()usesperm templ.name = ?for SSO permission template mapping.findGroupByName()usesuser 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.
Patches
Fixed 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 / Credit
whale120 (@whale120 tw), working with DEVCORE Internship Program
Fix
Improper Authentication
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Poweradmin/Poweradmin