Poweradmin mapeia identidades OIDC em usuários locais através de `oidc_user_links.oidc_subject` mais `provider_id`. No esquema MySQL, a tabela de links OIDC usa explicitamente `utf 8 mb 4 _ unicode_ci`, que é insensível a caso e insensível a sotaque. O sub- sub- OIDC é um identificador de assunto externo estável e deve ser combinado byte- for-byte dentro do escopo emitente/ fornecedor. O PoC local confirmado usou dois usuários diferentes da OIDC. - Sujeito vítima: `víctima- login` - Sujeito atacante: `víctím-login` (`í`, U+ 00 ED) MySQL relatou esses dois assuntos como iguais em ` utf 8 mb 4 _unicode_ci`. Após a vítima ligar sua conta OIDC, o atacante autenticou- se com o mesmo provedor com a senha do atacante e Poweradmin resolveu a sessão para a conta local da vítima. - **Aplicação:** Poweradmin - **Versão:** `alvos/poweradmin` gt `e 1 f 9 c 9 a` - ** Base de dados:** MySQL ` 8.4.10 `, ` caracter_set_server=utf 8 mb 4 `, `collation_server=utf 8 mb 4 _unicode_ci` - **Permissões de acesso:** Qualquer usuário que possa criar ou controlar uma conta no provedor OIDC conectado - **Método de Autidade:** Provedor genérico OIDC - **Ferramentas:** Docker Compose, provedor local OIDC, arnês Python PoC **Ponto de Entrada Afectado:**. ``` texto GET /oidc/ login?provider=genérico GET /oidc/ callback?code=...&state=... ``` Propriedades relevantes do pedido. - Autenticação: fluxo de código de autorização da OIDC válido - Trigger: atacante A conta da OIDC tem um `sub` que colapsa com o `sub` de uma vítima - campo Vulnerável: OIDC `sub` armazenado e procurado como `oidc_user_links.oidc_subject`.

Análise de Causa Raiz. ### parte 0 — `oidc_user_links.oidc_subject` usa uma colação insensível ao acento O esquema MySQL define a tabela de links OIDC com o ` utf 8 mb 4 _unicode_ci`: ``` sql -- sql/poweradmin-mysql- db- structure.sql: 341 - 356 CREA A TABELA `oidc_user_links` ( `user_id` INT( 11 ) NÃO NULL, ` provider_id` VARCHAR( 50 ) NÃO NULL, ` oidc_subject` VARCHAR( 255 ) NÃO NULL,... CLEF ÚNICO ` unique_subject_provider` ( ` oidc_subject`, `provider_id`) ) INGINE=InnoDB DEFAULT CHARSET=utf 8 mb 4 COLLATE=utf 8 mb 4 _ unicode_ci; ``` No banco de dados PoC. ``` Texto Tipo de Campo Collação provedor_id varchar( 50 ) utf 8 mb 4 _unicode_ci oidc_subject varchar( 255 ) utf 8 mb 4 _unicode_ci SELECCIONE 'víctim- login' = 'víctim- login' COLLAR 8 mb 4 _unicode_ci; -- acentuação_colisão = 1 ``` ### parte 1 — O sub- OIDC flui para uma busca normal de igualdade SQL.

O Poweradmin lê o assunto a partir de dados de informação de usuário da OIDC. Se nenhum mapeamento personalizado `sujeito` estiver configurado, ele usa a reivindicação `sub`: ```php // lib/ Application/ Service/ OidcService.php: 424 - 459 $resourceProv.Oner = $provider->getResourceProv.Oner($token); $userData = $resourceProv.Oner->toArray();... assunto: $userData[$mapping['sujeito']?? 'sub']?? '', ''', '`` O callback passa o resultado `OidcUserInfo` para provisão. ```php // lib/ Application/ Service/ OidcService.php: 269 - 291 $userInfo = $this->getUserInfo($provider, $token, $providerId); $userId = $this->userProvisioningService->provisionUser($userInfo, $providerId); ``` A provisão primeiro tenta encontrar um usuário existente por assunto. ```php // lib/Application/Service/ UserProvisioningService.php: 86 - 90 $existingUserId = $authMethod === self:::AUTH_METHOD_SAML? $this->findUserBySamlSubject($userInfo->getSubject(), $providerId): $this->findUserByOidcSubject($userInfo->getSubject(), $providerId); ``` A busca é a igualdade SQL normal avaliada sob a colação fraca da coluna: ```php // lib/Application/Service/ UserProvisioningService.php: 145 - 149 $stmt = $this->db->prepare(" SELECIONE user_id DE oidc_user_links Onde oidc_sujeito =? E provider_id =? "); $stmt->execute([$subject, $providerId]); ```.

Quando o atacante se autenticar com `sub = victim- login`, o MySQL corresponde à linha existente para ` oidc_subject = vitima- login` e devolve o ` user_id' da vitima. ### parte 2 — O ID de usuário devolvido torna-se a sessão autenticada Após o provimento devolver o 'user_id' correspondente, o PowerAdmin recupera o nome de usuário do banco de dados para aquele usuário e armazena o ID de usuário correspondente na sessão: ```php // lib/ Application/ Service/ OidcService.php: 307 - 317 $databaseNome de usuário = $this->userProvisioningService->getDatabaseNome de usuário($useId); $this->setSessionValue('userlogin', $databaseNome de usuário); ``` ```php // lib/ Application/ Service/ OidcService.php: 360 - 371 $this->setSessionValue('userid', $userId);... $this->setSessionValue('autenticado', true); `` A senha do atacante OIDC é validada pelo IdP, mas o local ` user_id` selecionado pelo Poweradmin vem da consulta SQL de colação fraca.

Um atacante que pode registrar ou controlar um principal da OIDC com uma variante de sotaque/colação do sujeito da OIDC de uma vítima pode autenticar com as próprias credenciais de IdP do atacante e obter uma sessão Poweradmin para a conta local da vítima. O PoC confirmado usou nomes de usuário, assuntos, e- mails e senhas distintos da OIDC. O atacante não sabia ou modificou a senha da vítima. ### 1. Iniciar o laboratório local do OIDC Poweradmin. ``` Bash sudo -n docker compose -p poweradminoidcpoc -f poc/work/poweradmin-oidc-collation/docker-composte.yml up -d ``` O laboratório usa o MySQL ` utf 8 mb 4 _unicode_ci` e um provedor local da OIDC com duas contas de login reais: ``` texto Vítima: nome de usuário/ sub: vítima- login e-mail: victim.poweradmin@exemplo.com senha: vítimaPassword 123! Atacador: nome de usuário/ sub: victím- login e- mail: attacater.poweradmin@exemplo.com senha: AttacadorPassword 123! ```.

### 2. Faça login uma vez como vítima através do OIDC. Autenticar através de `GET /oidc/ login?provider=genérico` usando: ````texto nome de usuário: senha de login da vítima: VictimaPassword 123! ```. Poweradmin cria o usuário local vítima e link OIDC: ````text users: id username fullname email 2 vítima-login vítima Poweradmin victim.poweradmin@exemplo.com. links_de_utilizador: id user_id provider_id oidc_sujeito 1 2 login de vítima genérico ``` ### 3. Faça login como o usuário atacante da OIDC. Autenticar a partir de uma sessão de navegador separada com: ````texto nome de usuário: senha do login do vídeo: AttackerPassword 123! ```. Resultado observado de `poc/work/poweradmin-oidc- collation/run_poc.py`:.

```json { "atacker_login": { "selected_idp_user": "atacker", "selected_idp_username": "victím-login", "has_session_cookie": true, "home_contienes_victim_username": true, "home_contienes_atacker_username: false }, "atacker_resolved_to_victim": true } ```` Evidências do banco de dados após ambos os logins. ``` versão text charset_server collation_server 8.4.10 utf 8 mb 4 utf 8 mb 4 _unicode_ci usuários: nome de usuário de id e- mail completo 2 vítima-login vítima Poweradmin victim.poweradmin@exemplo.com oidc_user_links: id user_id provider_id oidc_subject oidc_subject_hex 1 2 login- vítima genérico 76696374696 D 2 D 6 C 6 F 67696 E ``` O log de auditoria de aplicativos também registra todos os eventos de login da OIDC como usuário da vítima:.

```text user:victim- login operation:login_success auth_method:oidc ```. Nenhum usuário local ou link OIDC foi criado para `victím- login`. Trate os identificadores de assuntos da OIDC como strings exatos de byte. Para MySQL, migrar os identificadores de mapeamento OIDC para uma colação binária ou de preservação de bytes: ``` sql ALTER TABLE oidc_user_links MODIFY COLUNN provider_id varchar( 50 ) CARACTERISTICA utf 8 mb 4 COLLAR utf 8 mb 4 _bin NÃO NULL, MODIFICAR COLUMANO oidc_subject varchar( 255 ) CARACTERISTICA utf 8 mb 4 COLLAR utf 8 mb 4 _BIN NÃO UMULLO; ``` Também faça consultas de busca preservando byte para que o código de aplicativo patcheado proteja as implantações existentes antes que as migrações de esquemas estejam completas:.

```php $stmt = $this->db->prepare(" SELECIONE user_id DE OIDC_user_links Onde oidc_sujeito BINARY = BINARY? E BINARY provider_id = BINARY? "); ``` Revisar as buscas relacionadas de identidade e autorização. - `findUserByEmail()` usa `users.email =?` e pode ser impactado quando `link_by_email` está ativado. - `findPermissionTemplateByName()` usa `perm_templ.name =?` para o mapeamento de modelos de permissão SSO. - `findGroupByName()` usa `user_groups.name =?` para o mapeamento de grupos SSO. Esses campos devem ser documentados intencionalmente como caso/ insensível acentuado ou migrados/ procurados com semântica preservando bytes onde eles representam limites de segurança. Fixado 4.2.5, 4.3.4, e 4.4.0. Os identificadores de assuntos OIDC e SAML são agora compatíveis byte- for-byte. A correção inclui uma migração de banco de dados que altera a colação das colunas de link de identidade, por isso a atualização requer a execução do script de atualização SQL para o seu banco de dados na pasta `sql/`. ## Reconhecer / Crédito. baleia 120 (@ balança 120 _tw), trabalhando com o Programa de Práticas DEVCORE.

Registro de aconselhamento: GHSA-cmwh-g 2 h 8 - c 222. Não há nenhum identificador adicional listado. Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 24 T 21: 56: 02.000 Z e lista a sua última modificação como 2026 - 07 - 24 T 21: 56: 02.000 Z. Severidade: ALTAMENTE. Dados de pontuação publicados: CVSS_V 3: CVSS: 3.1 /AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N. Software afetado e informações de versão: Packagist package poweradmin/poweradmin — ECOSYSTEM: introduzido 4.1.0, corrigido 4.2.5. Packagist package poweradmin/ poweradmin — ECOSYSTEM: introduzido 4.3.0, corrigido 4.3.4. Classificação e evidência: identificadores de fraqueza CWE- 287. O registro contém 6 suporte de referências nestes tipos: WEB, PACKAGE.