
CVE-2026-11837: escalada local de privilégios no módulo ansible.posix authorized_key por meio de chown que segue symlinks. Análise técnica; irmã do CVE-2024-9902.
authorized_key do ansible.posixEscalonamento de privilégios local no módulo Ansible ansible.posix.authorized_key por meio de chown que segue symlinks (e criação de arquivo que segue symlinks) no diretório ~/.ssh do usuário e no arquivo authorized_keys.
Este é um relato de uma vulnerabilidade que reportei e à qual foi atribuído o CVE-2026-11837 pela Red Hat Product Security (a CNA). Está publicado aqui como registro técnico, não como alegação de novidade: o problema é irmão do CVE-2024-9902 em um módulo que a correção anterior não cobriu.
Reconhecimento publicado pela Red Hat: "A Red Hat gostaria de agradecer a Valentino Paulon por relatar este problema."
| CVE | CVE-2026-11837 |
| Componente | coleção ansible.posix, arquivo plugins/modules/authorized_key.py, função keyfile() |
| Tipo | Elevação de Privilégio (local). CWE-59 / CWE-61 (seguimento de links) com CWE-282 (gerenciamento inadequado de propriedade) |
| CVSS | 7.3 (Alto) |
| Público | 2026-06-10 |
| Reportado por | Valentino Paulon |
Quando um playbook executado como root usa ansible.posix.authorized_key para gerenciar as chaves de um usuário local, o helper keyfile() do módulo:
~/.ssh do usuário e do arquivo ~/.ssh/authorized_keys com os.chown simples (não os.lchown), eopen(..., "w") simples (sem O_NOFOLLOW).Com o padrão follow=False, o módulo nem segue-e-substitui nem recusa symlinks; ele opera diretamente no caminho, então o kernel segue qualquer symlink que o usuário alvo sem privilégios tenha pré-posicionado dentro do seu próprio ~/.ssh. Isso produz duas primitivas que transferem a propriedade de um caminho controlado pelo root para o usuário sem privilégios, que então eleva para root. Ambas foram reproduzidas de ponta a ponta contra a coleção em HEAD.
keyfile() (plugins/modules/authorized_key.py), com o padrão manage_dir=True, follow=False:
if manage_dir:
if not os.path.exists(sshdir): # os.path.exists FOLLOWS symlinks
try:
os.mkdir(sshdir, int('0700', 8))
...
os.chown(sshdir, uid, gid) # UNCONDITIONAL, plain chown, follows symlink
os.chmod(sshdir, int('0700', 8)) # follows symlink
if not os.path.exists(keysfile): # follows symlink
...
f = open(keysfile, "w") # plain open(), no O_NOFOLLOW, follows symlink
...
try:
os.chown(keysfile, uid, gid) # UNCONDITIONAL, plain chown, follows symlink
os.chmod(keysfile, int('0600', 8)) # follows symlink
except OSError:
pass
sshdir e keysfile são derivados do home passwd do usuário alvo (pwd.getpwnam(user).pw_dir), um diretório que o usuário alvo sem privilégios possui e controla. O parâmetro follow (padrão False) apenas escolhe se deve aplicar os.path.realpath() ao caminho; em nenhum dos casos há uma verificação os.path.islink, uma abertura com O_NOFOLLOW, um os.lchown ou um desfazer-o-link-e-substituir de um symlink pré-existente. Nenhuma redução de privilégio (seteuid/setfsuid) é realizada; tudo é executado como root.
As duas chamadas a os.chown são executadas incondicionalmente, fora das cláusulas de guarda if not os.path.exists(...), portanto disparam independentemente de o diretório/arquivo já existir. É isso que torna ambas as primitivas confiáveis.
Ambas confirmadas de ponta a ponta em uma VM Linux descartável, com ansible.posix em HEAD. victim é um usuário local sem privilégios (uid 1000). O módulo é executado como root, exatamente como o playbook de um operador faria. Canários servem como substitutos para alvos sensíveis do root.
Vetor 1: symlink de diretório (chown de um diretório existente pertencente ao root)
victim: ln -s /root/AD_CANARY ~/.ssh (com /root/AD_CANARY sendo um diretório existente pertencente ao root).authorized_key para victim./root/AD_CANARY mudou de root:root para victim:victim. A chamada os.chown(sshdir, ...) seguiu o symlink ~/.ssh e entregou um diretório pertencente ao root ao usuário sem privilégios.Vetor 2: symlink de arquivo quebrado (criação + chown de um novo arquivo em caminho do root)
victim (com ~/.ssh sendo um diretório normal): ln -s /root/AF_CANARY ~/.ssh/authorized_keys (o alvo não existe).authorized_key para victim./root/AF_CANARY foi criado, pertencente a victim:victim, modo 0600. open(keysfile, "w") criou o alvo através do symlink e os.chown(keysfile, ...) o entregou.Em ambos os casos, o usuário sem privilégios acaba possuindo um caminho controlado pelo root. Apontar o link para um diretório em /etc (Vetor 1) ou para um novo arquivo em /etc/cron.d/ ou /etc/sudoers.d/ (Vetor 2) resulta em root pelo encadeamento padrão, em que o usuário, agora proprietário, reescreve o alvo.
Este é um irmão do CVE-2024-9902 (o generate_ssh_key do módulo user, no ansible-core), que abordou a mesma classe de seguir symlinks. authorized_key reside na coleção separada ansible.posix e não foi alterado por aquela correção; o padrão de chown/criação seguindo symlinks ainda estava presente e sem proteção. É reportado como a mesma classe aceita em um módulo que a correção anterior não alcançou, não como uma causa raiz nova.
ansible.posix.authorized_key que tenha como alvo a conta da vítima (o uso comum deste módulo). O usuário local pré-posiciona o symlink no seu próprio ~/.ssh; ele não executa o playbook. Isso é acionado pelo operador: o mesmo modelo de acionamento do CVE-2024-9902, que a Red Hat aceitou com a mitigação "não executar contra contas não confiáveis". A pré-condição é declarada explicitamente, em vez de classificada como autoativada.os.open(keysfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) e operar no descritor; recusar (ou desfazer o link e substituir, conforme o contrato do docstring de follow=False) se sshdir ou keysfile for um symlink.os.chown(...) por os.lchown, ou por os.fchown em um descritor aberto com segurança, e igualmente os.fchmod em vez de os.chmod, para que a propriedade e as permissões não possam ser redirecionadas através de um link.chown/chmod incondicional para que eles se apliquem apenas a um caminho que o próprio módulo acabou de criar com segurança, não a um caminho pré-existente (possivelmente com symlink).| Data | Evento |
|---|---|
| 2026-06-08 | Reportado a [email protected] (Red Hat Product Security em CC como CNA), enquadrado como o irmão do CVE-2024-9902 |
| 2026-06-10 | CVE-2026-11837 atribuído e publicado; crédito aceito |
MIT. Veja LICENSE.