
MySQL Community Server 26.7.0 - Alta - SET ROLE ALL EXCEPT resíduo FILE - gravação mysqld UID
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · mysql-set-role-leftover
Classe: Privilégio residual (FILE como UID do mysqld; não é LPE do SO) Alcance: Remoto
MySQL Community Server mysqld 26.7.0 (06a5c1c) - Oracle
SET ROLE ALL EXCEPT rfile (ou SET ROLE DEFAULT sem padrões) limpa a lista de roles ativas, então checkout_access_maps() retorna sem set_master_access(user->access). CURRENT_ROLE() é NONE. Bits globais estáticos que a role agregou (FILE, PROCESS, SUPER, ...) permanecem em m_master_access. check_access lê esse resíduo. Lab: FILE residual escreve INTO OUTFILE como o UID do mysqld.
SET ROLE NONE é o caminho que restaura ACL_USER::access. Privilégios dinâmicos (SYSTEM_USER) caem. O resíduo em nível de DB já está fechado (Bug#35386565). Contas padrão não têm roles.
| ID | sem CVE ainda |
| Classe | Privilégio residual (FILE como UID do mysqld; não é LPE do SO) |
| Alcance | Remoto (SQL autenticado) |
| CWE | CWE-269 |
| CVSS | Alto: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Produto | MySQL Community Server mysqld |
| Afetado | 26.7.0 (06a5c1c99c377fc41b2eba1ea244e8b220bdc3c8) |
| Auth | autenticado; a vítima recebe uma role que carrega ACLs globais estáticas (FILE no lab) |
| Licença | GNU Affero GPL v3.0 |
| Lab | apenas 127.0.0.1. Arquivo testemunha. Escrita FILE residual. |
Possuir uma conta que recebeu uma role com FILE (ou outro bit global estático). SET ROLE nessa role, depois SET ROLE ALL EXCEPT nela. CURRENT_ROLE() reporta NONE. SELECT 'witness' INTO OUTFILE ainda escreve como o UID do mysqld sob secure_file_priv.
Uma aplicação que faz sandbox com SET ROLE ALL EXCEPT dangerous_role ou SET ROLE DEFAULT e então confia em CURRENT_ROLE() ainda tem esses bits estáticos. SET ROLE NONE de fato os remove.
O resíduo não concede FILE à conta que nunca o teve através de uma role. Ele falha em remover FILE após a desativação.
Mesma caçada na 26.7.0 que os três resíduos de cliente. O daemon padrão não publicado Crit/High estava vazio. ACL / roles ainda tinha esse early return de role vazia.
void Security_context::checkout_access_maps(void) {
if (m_acl_map != nullptr) {
get_global_acl_cache()->return_acl_map(m_acl_map);
m_acl_map = nullptr;
}
if (m_active_roles.size() == 0) return;
// ...
}
activate_role_all (ALL EXCEPT) e activate_role_default vazio chamam esse checkout. activate_role_none então faz set_master_access(user->access).
Caminho errado já registrado: o primeiro resíduo SELECT note FROM lab.t INTO OUTFILE retornou 1142 (db_acl() cache 0). O SELECT 'witness' INTO OUTFILE constante é o oráculo de FILE.
cd lab
./run.sh
Imagem mysql:26.7.0. Publicado 127.0.0.1:18630. A role rfile tem FILE. A vítima tem GRANT SELECT ON lab.* e GRANT rfile, file_priv=N, sem role padrão. Login outfile 1227. SET ROLE rfile outfile ok. SET ROLE ALL EXCEPT rfile faz CURRENT_ROLE() NONE; o outfile residual escreve /var/lib/mysql-files/MYSQL-SET-ROLE-LEFTOVER-WITNESS. SET ROLE NONE é 1227 de novo.
SUCCESS mysql-set-role-leftover before-file=denied with-role=yes leftover-role=NONE leftover-file=yes after-none=denied dump=26.7.0 image=mysql:26.7.0 MYSQL-SET-ROLE-LEFTOVER-WITNESS
Quando m_active_roles está vazio, checkout_access_maps() deveria fazer set_master_access(user->access) da mesma forma que SET ROLE NONE já faz. ALL EXCEPT / DEFAULT vazio não deveria manter o mapa agregado anterior.
06a5c1c99c377fc41b2eba1ea244e8b220bdc3c8)sql/auth/sql_security_ctx.cc checkout_access_mapssql/auth/roles.cc activate_role_all / activate_role_none