
CVE-2026-11837: escalación de privilegios local en el módulo authorized_key de ansible.posix mediante chown que sigue enlaces simbólicos. Informe técnico; hermano de CVE-2024-9902.
authorized_key de ansible.posixEscalada de privilegios local en el módulo Ansible ansible.posix.authorized_key mediante chown que sigue enlaces simbólicos (y creación de archivos que sigue enlaces simbólicos) en el directorio ~/.ssh del usuario y el archivo authorized_keys.
Esta es una descripción de una vulnerabilidad que reporté y que fue asignada CVE-2026-11837 por Red Hat Product Security (el CNA). Se publica aquí como registro técnico, no como una reclamación de novedad: el problema es el gemelo de CVE-2024-9902 en un módulo que la corrección anterior no cubría.
Agradecimiento publicado por Red Hat: "Red Hat quisiera agradecer a Valentino Paulon por reportar este problema."
Cuando un playbook que se ejecuta como root usa ansible.posix.authorized_key para gestionar las claves de un usuario local, la función auxiliar keyfile() del módulo:
~/.ssh del usuario y del archivo ~/.ssh/authorized_keys con os.chown simple (no os.lchown), yopen(..., "w") simple (sin O_NOFOLLOW).Con el valor predeterminado follow=False, el módulo ni sigue y reemplaza ni rechaza los enlaces simbólicos; opera directamente sobre la ruta, por lo que el kernel sigue cualquier enlace simbólico que el usuario objetivo no privilegiado haya colocado previamente dentro de su propio ~/.ssh. Esto produce dos primitivas que transfieren la propiedad de una ruta controlada por root al usuario no privilegiado, quien luego escala a root. Ambas fueron reproducidas de extremo a extremo contra la colección en HEAD.
keyfile() (plugins/modules/authorized_key.py), con los valores predeterminados manage_dir=True, follow=False:
if manage_dir:
if not os.path.exists(sshdir): # os.path.exists SIGUE enlaces simbólicos
try:
os.mkdir(sshdir, int('0700', 8))
...
os.chown(sshdir, uid, gid) # INCONDICIONAL, chown simple, sigue enlace simbólico
os.chmod(sshdir, int('0700', 8)) # sigue enlace simbólico
if not os.path.exists(keysfile): # sigue enlace simbólico
...
f = open(keysfile, "w") # open() simple, sin O_NOFOLLOW, sigue enlace simbólico
...
try:
os.chown(keysfile, uid, gid) # INCONDICIONAL, chown simple, sigue enlace simbólico
os.chmod(keysfile, int('0600', 8)) # sigue enlace simbólico
except OSError:
pass
sshdir y keysfile se derivan del directorio home del usuario objetivo (pwd.getpwnam(user).pw_dir), un directorio que el usuario objetivo no privilegiado posee y controla. El parámetro follow (valor predeterminado False) solo elige si se debe ejecutar os.path.realpath() en la ruta; en ningún caso hay una verificación de os.path.islink, una apertura con O_NOFOLLOW, un os.lchown, o un desenlace y reemplazo de un enlace simbólico preexistente. No se realiza ninguna reducción de privilegios (seteuid/setfsuid); todo se ejecuta como root.
Los dos llamados a os.chown se ejecutan incondicionalmente, fuera de las comprobaciones if not os.path.exists(...), por lo que se activan independientemente de si el directorio/archivo ya existía. Eso es lo que hace que ambas primitivas sean confiables.
Ambas confirmadas de extremo a extremo en una máquina virtual Linux desechable, con ansible.posix en HEAD. victim es un usuario local no privilegiado (uid 1000). El módulo se ejecuta como root, exactamente como lo haría el playbook de un operador. Los canarios representan objetivos root sensibles.
Vector 1: enlace simbólico de directorio (chown de un directorio existente propiedad de root)
victim: ln -s /root/AD_CANARY ~/.ssh (con /root/AD_CANARY un directorio existente propiedad de root).authorized_key para victim./root/AD_CANARY cambió de root:root a victim:victim. El llamado a os.chown(sshdir, ...) siguió el enlace simbólico ~/.ssh y entregó un directorio propiedad de root al usuario no privilegiado.Vector 2: enlace simbólico de archivo colgante (creación + chown de un nuevo archivo en una ruta root)
victim (con ~/.ssh un directorio normal): ln -s /root/AF_CANARY ~/.ssh/authorized_keys (el destino no existe).authorized_key para victim./root/AF_CANARY fue creado, propiedad victim:victim, modo 0600. open(keysfile, "w") creó el destino a través del enlace simbólico y os.chown(keysfile, ...) lo entregó.En ambos casos, el usuario no privilegiado termina poseyendo una ruta controlada por root. Apuntar el enlace a un directorio bajo /etc (Vector 1) o a un nuevo archivo bajo /etc/cron.d/ o /etc/sudoers.d/ (Vector 2) produce acceso root mediante el seguimiento estándar, donde el usuario, ahora propietario, reescribe el objetivo.
Este es un gemelo de CVE-2024-9902 (el módulo user's generate_ssh_key, en ansible-core), que abordaba la misma clase de seguimiento de enlaces simbólicos. authorized_key reside en la colección separada ansible.posix y no fue modificado por esa corrección; el patrón de chown/creación que sigue enlaces simbólicos aún estaba presente y sin protección. Se reporta como la misma clase aceptada en un módulo al que la corrección anterior no llegó, no como una causa raíz novedosa.
ansible.posix.authorized_key que tenga como objetivo la cuenta de la víctima (el uso común de este módulo). El usuario local coloca previamente el enlace simbólico en su propio ~/.ssh; no ejecuta el playbook. Esto es desencadenado por el operador: el mismo modelo de desencadenamiento que CVE-2024-9902, que Red Hat aceptó con la mitigación "no ejecutar contra cuentas no confiables". La condición previa se establece explícitamente en lugar de calificarse como autodesencadenada.os.open(keysfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) y operar sobre el descriptor; rechazar (o desenlazar y reemplazar, según el contrato de follow=False de la documentación) si sshdir o keysfile es un enlace simbólico.os.chown(...) con os.lchown, o con os.fchown en un descriptor abierto de forma segura, y de manera similar os.fchmod en lugar de os.chmod, para que la propiedad y los permisos no puedan redirigirse a través de un enlace.chown/chmod incondicional para que solo se aplique a una ruta que el propio módulo haya creado de forma segura, no a una ruta preexistente (posiblemente enlazada simbólicamente).| Fecha | Evento |
|---|---|
| 2026-06-08 | Reportado a [email protected] (con copia a Red Hat Product Security como CNA), presentado como el gemelo de CVE-2024-9902 |
| 2026-06-10 | CVE-2026-11837 asignado y publicado; crédito aceptado |
MIT. Ver LICENSE.
| CVE | CVE-2026-11837 |
| Componente | Colección ansible.posix, archivo plugins/modules/authorized_key.py, función keyfile() |
| Tipo | Elevación de privilegios (local). CWE-59 / CWE-61 (seguimiento de enlaces) con CWE-282 (gestión inadecuada de la propiedad) |
| CVSS | 7.3 (Alta) |
| Pública | 2026-06-10 |
| Reportado por | Valentino Paulon |