
Un informe sobre Dirty Frag, que es una cadena de vulnerabilidades de escalada de privilegios local (LPE) en Linux que permite a un usuario sin privilegios obtener acceso root.
Laboratorio de reproducción y detección de exploits para una cadena de escalada de privilegios local en el kernel de Linux.
Estado: VERIFICADO. Completé la reproducción, la verificación sin archivos y la detección a nivel de llamadas al sistema en el laboratorio (kernel 6.18.9+kali-amd64). Este documento es un registro de laboratorio. Cada afirmación a continuación fue observada durante la ejecución de la reproducción. Las capturas de pantalla y los artefactos son capturas reales de la máquina virtual.
Dirty Frag combina dos errores lógicos deterministas en el kernel de Linux. Estos errores permiten que un usuario local sin privilegios sobrescriba la caché de páginas de archivos de solo lectura (por ejemplo, /usr/bin/su) y obtenga un shell root:
Ambas variantes usan el mismo patrón raíz que Dirty Pipe y Copy Fail. La llamada al sistema splice(2) coloca una referencia a una página de caché de páginas de un archivo en la ranura frag de un sk_buff del lado del remitente. El atacante solo puede leer este archivo. El código del kernel del lado receptor luego realiza un STORE criptográfico in situ sobre ese frag. Esto muta la caché de páginas en RAM. No ocurre ninguna escritura en disco, por lo que el monitoreo de integridad de archivos (AIDE, Tripwire) no puede verlo. El ataque es determinista. No tiene ventana de carrera ni pánico del kernel en caso de fallo.
cac2661c53f3 (2017‑01) hasta f4c50a4034e6 (parcheado 2026‑05‑05)2dc334f1a63a (2023‑06) hasta aa54b1d27fe0 (parcheado 2026‑05‑10)CVE-2026-43284 = 8.8 (Alta), CVE-2026-43500 = 7.8 (Alta)Dirty Frag es un LPE sin archivos. Corrompe la caché de páginas en memoria, no el archivo en disco. El monitoreo tradicional de integridad de archivos no puede verlo. La detección debe ocurrir en la capa de llamadas al sistema. La cadena usa estas primitivas de llamadas al sistema: socket(AF_ALG)/socket(AF_RXRPC), splice y unshare(CLONE_NEWUSER|CLONE_NEWNET). La ruta ESP también crea sockets UDP AF_INET y netlink. Esta capa es el enfoque de la ingeniería de detección en este repositorio.
Ambas variantes usan el mismo sumidero: criptografía in situ que ALMACENA bytes en una página de caché de páginas que el atacante coloca con splice(2).
UDP_ENCAP_ESPINUDP.vmsplice, luego 16 bytes de /usr/bin/su en el desplazamiento del archivo objetivo con splice.splice empuja la tubería hacia el socket de envío. splice_to_socket() establece MSG_SPLICE_PAGES. Esto coloca la página de caché de páginas de /usr/bin/su directamente en skb->frags[0].xfrm4_udp_encap_rcv, luego xfrm_input, luego esp_input(). La rama vulnerable skip_cow () omite . Realiza con la página de caché de páginas como origen y destino.El atacante controla tanto la ubicación (desplazamiento de splice) como el valor (4 bytes). La verificación de autenticación se ejecuta después del store, por lo que la capa criptográfica nunca marca la escritura. Esta variante requiere CAP_NET_ADMIN y usa unshare(CLONE_NEWUSER|CLONE_NEWNET).
rxkad_verify_packet_1() realiza un descifrado pcbc(fcrypt) de un solo bloque directamente sobre el frag skb fijado con splice. No copia los datos primero. El atacante elige una clave de sesión (add_key("rxrpc", …)) para que decrypt(ciphertext) sea igual a desired_plaintext. Esto produce un STORE de 8 bytes. Esta variante apunta a /etc/passwd. No necesita espacio de nombres de usuario. Requiere el módulo rxrpc.ko (cargado por defecto en Ubuntu).
El PoC público apunta a /usr/bin/su. Escribe 48 stores ESP de 4 bytes cada uno (192 bytes en el desplazamiento de archivo 0). Reemplaza los primeros bytes de la caché de páginas con un ELF estático de shell root. El punto de entrada del ELF ejecuta setgid(0); setuid(0); setgroups(0,NULL); execve("/bin/sh", …). Un solo execve("/usr/bin/su") luego produce un shell root.
El parche ESP (mainline f4c50a4034e6) marca los frags de página que llegan a través de splice() con la bandera SKBFL_SHARED_FRAG. La rama skip_cow en esp_input() ahora también verifica esta bandera. Los skbs con frags compartidos pasan por skb_cow_data() antes del descifrado AEAD in situ.
El parche RxRPC (mainline aa54b1d27fe0) agrega una verificación de skb->data_len junto a la verificación existente de skb_cloned(). El kernel copia un skb no lineal con datos paginados antes del descifrado pcbc(fcrypt) in situ.
Captura de pantalla de la configuración del laboratorio VirtualBox:

.
├── README.md # este registro de laboratorio
├── detection/
│ ├── dirtyfrag.rules # reglas de detección a nivel de llamadas al sistema auditd
│ ├── ausearch_dirtyfrag_observed.txt # salida real de detección del exploit
│ ├── sigma/
│ │ └── dirty_frag_exploit.yml # regla Sigma para detección SIEM
│ └── yara/
│ └── dirty_frag_exploit.yar # regla YARA para código PoC en disco/memoria
├── mitigation/
│ └── dirtyfrag_mitigation.sh # lista negra de módulos + vaciado de caché de páginas
├── poc/
│ └── check_vulnerable.py # verificador previo no destructivo
├── reports/
│ └── incident-dirtyfrag.md # manual de respuesta a incidentes
└── screenshots/ # capturas reales de la VM de laboratorio
poc/check_vulnerable.py y confirmar kernel/módulos/usernstestuser sin privilegios/usr/bin/su corrupto y restauradodrop_caches o reinicio)detection/dirtyfrag.rules y validar alertas de auditdcat /etc/os-release | head -3
uname -r
Captura de pantalla de la verificación de la versión del kernel:

El kernel debe ser anterior a las correcciones de mayo de 2026 (f4c50a4034e6 / aa54b1d27fe0). Si ejecutó apt upgrade después de esa fecha, el exploit fallará. Consulte Solución de Problemas.
Un verificador seguro informa si la VM es un objetivo plausible. Verifica el kernel en ejecución, la presencia de los módulos esp4/esp6/rxrpc y si los espacios de nombres de usuario sin privilegios están disponibles. La variante ESP requiere estos espacios de nombres.
python3 poc/check_vulnerable.py
Veredicto esperado: [*] potencialmente vulnerable -- proceda solo en una VM desechable.
Un testuser sin privilegios simula un atacante sin derechos especiales.
sudo useradd -m testuser
sudo passwd testuser
su - testuser
id
Captura de pantalla: id muestra UID 1001. Esto confirma acceso sin root.

Desde la cuenta sin privilegios:
git clone https://github.com/V4bel/dirtyfrag.git
cd dirtyfrag
gcc -O0 -Wall -o exp exp.c -lutil
./exp
En caso de éxito, el exploit parchea la caché de páginas de /usr/bin/su. Escribe 48 stores ESP de 4 bytes cada uno (un ELF de shell root de 192 bytes en el desplazamiento de archivo 0). Luego suelta un shell root interactivo con forkpty.
Captura de pantalla de disponibilidad de módulos, revisión del código fuente y compilación limpia:

id
whoami
Captura de pantalla: id muestra uid=0(root) después de ./exp.

sha256sum lee a través de la caché de páginas. Mientras la escritura del exploit está activa, el hash es diferente del original. Después de un vaciado, el hash vuelve al original. Este par antes/después demuestra que el binario en disco nunca fue tocado.
sha256sum /usr/bin/su # 1) mientras la caché de páginas está contaminada -> hash DIFERENTE
Captura de pantalla: el hash es diferente del hash del paquete (RAM envenenada, disco intacto).

Hash corrupto observado (caché de páginas): 3fc29078bd77150b5d6fbb368f632bf02c1a3704816f03fb694ec2e81e77bde4
Después de la explotación, la caché de páginas contiene los datos corruptos. Siempre vacíela:
echo 3 | sudo tee /proc/sys/vm/drop_caches
# o reinicie la VM
Luego verifique la restauración (como testuser):
sha256sum /usr/bin/su # ahora coincide con el hash ORIGINAL del paquete
su - # solicita contraseña nuevamente — sin root automático
Captura de pantalla: hash restaurado (original en disco).

Hash original observado (en disco): 2b4f8770bd35bba5cdc5cfe292bc1d988e92ec1786bf91cf83e0e86fac056eb6
Después de un reinicio, dpkg -V util-linux devolvió sin salida. El /usr/bin/su en disco coincide exactamente con el paquete, por lo que el hash corrupto 3fc29078… existía solo en la caché de páginas.
drop_cachespuede no expulsar la página envenenada. Esto ocurre si un proceso en ejecución aún mantiene fijada la página. En ese caso,sha256sumydpkg -Vseguirán mostrando el contenido corrupto. La solución confiable es un reinicio. Tenga en cuenta quedpkg -Vtambién lee a través de la caché de páginas. Mientras la página está envenenada, informa??5??????(el MD5 es diferente; tamaño, modo, propietario y mtime coinciden). Vuelve a estar en silencio después del reinicio. Eso demuestra que el archivo en disco nunca fue modificado.
El vaciado no deshabilita el exploit. Solo limpia la caché de páginas envenenada. Debe deshabilitar los módulos por separado (consulte Mitigación). La eliminación de módulos ya cargados en un host explotado requiere un reinicio.
Dirty Frag es invisible para el monitoreo de integridad de archivos. La detección se centra en las primitivas de llamadas al sistema que la cadena debe usar.
detection/dirtyfrag.rules)# /etc/audit/rules.d/dirtyfrag.rules
-a always,exit -F arch=b64 -S socket -F a0=38 -F uid!=0 -k dirtyfrag_af_alg
-a always,exit -F arch=b64 -S socket -F a0=33 -F uid!=0 -k dirtyfrag_rxrpc
-a always,exit -F arch=b64 -S splice -F uid!=0 -k dirtyfrag_splice
-a always,exit -F arch=b64 -S unshare -F uid!=0 -k dirtyfrag_namespace
-w /usr/bin/su -p r -k dirtyfrag_suid_read
Nota:
AF_ALG = 38yAF_RXRPC = 33en Linux (consulte/usr/include/bits/socket.h). Borradores anteriores usabana0=21. Ese valor es incorrecto. AF_RXRPC es 33, no 21.
Implementar y verificar:
sudo cp detection/dirtyfrag.rules /etc/audit/rules.d/
sudo systemctl restart auditd # o: sudo auditctl -D && sudo auditctl -R /etc/audit/rules.d/dirtyfrag.rules
sudo auditctl -l
Alertas esperadas después de re-ejecutar el exploit (correlacione por PID):
ausearch -k dirtyfrag_af_alg
ausearch -k dirtyfrag_rxrpc
ausearch -k dirtyfrag_splice
ausearch -k dirtyfrag_namespace
Las cinco reglas se cargaron (auditctl -R, res=1 para cada CONFIG_CHANGE). Las reglas capturaron la configuración de espacios de nombres del exploit:
time->Wed Aug 5 08:26:37 2026
type=PROCTITLE msg=audit(1785932797.328:592): proctitle="./exp"
type=SYSCALL msg=audit(1785932797.328:592): arch=c000003e syscall=272 success=yes exit=0 a0=50000000 a1=0 a2=0 a3=0 items=0 ppid=5198 pid=5199 auid=1000 uid=1001 gid=1001 euid=1001 suid=1001 fsuid=1001 egid=1001 sgid=1001 fsgid=1001 tty=pts2 ses=2 comm="exp" exe="/home/testuser/dirtyfrag/exp" subj=unconfined key="dirtyfrag_namespace"
Decodificado: syscall=272 (unshare), a0=50000000 es igual a CLONE_NEWUSER | CLONE_NEWNET, uid=1001 (testuser sin privilegios), comm="exp". El registro completo está en detection/ausearch_dirtyfrag_observed.txt.
Captura de pantalla: la salida de ausearch muestra la carga de reglas y el evento del exploit.

El manual completo está en reports/incident-dirtyfrag.md: resumen ejecutivo, cronología, IoCs, mapeo MITRE ATT&CK, contención, erradicación, recuperación y lecciones aprendidas.
Mitigación inmediata en tiempo de ejecución. No sobrevive a un reinicio para módulos ya cargados (consulte la nota a continuación):
sudo mitigation/dirtyfrag_mitigation.sh
Qué hace:
/etc/modprobe.d/dirtyfrag.conf para bloquear esp4, esp6 y rxrpc. Incluye las líneas blacklist y alias … off. Un one-liner simple omite estas líneas (la autocarga por alias aún funcionaba de otro modo).Impacto: deshabilitar estos módulos hace que la funcionalidad de VPN IPsec (ESP) y del sistema de archivos AFS (RxRPC) deje de funcionar.
Corrección permanente: actualice a un kernel que contenga los parches upstream. O ponga los módulos en lista negra al arrancar (initcall_blacklist=esp4,esp6,rxrpc).
Este repositorio es solo para investigación y capacitación de seguridad defensiva autorizada.
Consulte LICENSE. Este laboratorio es solo para uso educativo en su propio entorno aislado. No ejecute el PoC en sistemas que no esté autorizado a probar.
| Variante | CVE | Sumidero | Ruta de activación | ¿Necesita userns sin privilegios? |
|---|
| Escritura en Caché de Páginas xfrm‑ESP | CVE‑2026‑43284 | crypto_authenc_esn_decrypt() en esp_input() | socket(AF_INET) con UDP‑encap, luego xfrm_input() | Sí (CAP_NET_ADMIN) |
| Escritura en Caché de Páginas RxRPC | CVE‑2026‑43500 | rxkad_verify_packet_1() (pcbc(fcrypt)) | socket(AF_RXRPC) | No |
!skb_cloned() && !skb_has_frag_list()skb_cow_data()crypto_authenc_esn_decrypt() emite un STORE de los 32 bits de orden superior del ESN. Ese valor es replay_esn->seq_hi. El atacante elige este valor en el registro de SA con el atributo netlink XFRMA_REPLAY_ESN_VAL.| Componente | Detalles |
|---|
| Hipervisor | VirtualBox |
| VM objetivo | Kali Linux 2026.1 (instantánea restaurada a un estado vulnerable) |
| Kernel | 6.18.9+kali‑amd64 (anterior a las correcciones de mayo de 2026) |
| PoC del exploit | V4bel/dirtyfrag (archivo C único) |
| Detección | auditd (reglas en detection/dirtyfrag.rules) |
| Capa | Qué ve | Estado |
|---|
| auditd | Sockets AF_ALG y AF_RXRPC, splice, unshare, lecturas SUID | Sí. Implementado y validado (archivo de reglas detection/dirtyfrag.rules) |
| Sigma | Patrones de llamadas al sistema para SIEM | Sí. Regla lista (detection/sigma/dirty_frag_exploit.yml) |
| YARA | Código PoC en disco o en memoria | Sí. Regla lista (detection/yara/dirty_frag_exploit.yar) |
| FIM (AIDE/Tripwire) | Cambios de archivos | No. Ciego, porque no ocurre ninguna escritura en disco |
| Síntoma | Causa probable | Corrección |
|---|
./exp imprime failed / post-write verify failed | El kernel está parcheado (de mayo de 2026 o posterior) | Arranque un kernel anterior o restaure una instantánea previa a la actualización. Re-verifique con poc/check_vulnerable.py |
unshare(CLONE_NEWUSER) devuelve EPERM | Espacios de nombres de usuario sin privilegios deshabilitados (AppArmor o sysctl) | Verifique sysctl kernel.unprivileged_userns_clone. En Ubuntu, verifique AppArmor. Kali lo permite por defecto |
esp4/esp6/rxrpc no cargados | Módulos no disponibles | sudo modprobe esp4 esp6 rxrpc (RxRPC se autocarga con socket(AF_RXRPC)) |
Binario su "parcheado" pero sin shell root | drop_caches ya se ejecutó, o la caché de páginas está fijada por procesos | Re-ejecute el exploit. Si está atascado, reinicie la VM |
| El exploit se ejecutó, luego aún se puede obtener root después de la mitigación | Caché de páginas no vaciada, o módulos ya cargados antes de la lista negra | echo 3 > /proc/sys/vm/drop_caches. La detención completa requiere reinicio |