Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

FeedsContactoPrivacidad© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
ZigRaceExploit — POC de CVE-2021-25741 en Zig | Kitploit
Herramientas/GitHubGitHub/glutenfree69/zigraceexploit
Seguridad de ContenedoresAnálisis de VulnerabilidadesExplotaciónSeguridad en la NubeAprendizaje y EducaciónExplotación de Binarios
GitHubglutenfree69/zigraceexploit

ZigRaceExploit

POC de CVE-2021-25741 en Zig

Ver Repositorio
28hace 7 mesesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2021-25741 — Explotación de TOCTOU Symlink Race en Zig

Exploit educativo de la condición de carrera TOCTOU (Time Of Check, Time Of Use) que está en el origen de CVE-2021-25741, una vulnerabilidad en el kubelet de Kubernetes.

El proyecto demuestra localmente (sin clúster K8s) cómo un intercambio concurrente de symlink puede saltarse una verificación de ruta entre lstat() y open(), y prueba que openat2() con los flags RESOLVE_* es la solución.

Tabla de contenidos

  • El problema
  • Cómo funciona el exploit
  • Arquitectura del código
  • Requisitos previos
  • Build
  • Uso
    • Prueba local con Docker
    • Comparación openat vs openat2
    • Jugar con el retardo TOCTOU
    • Prueba en un nodo Kubernetes real
  • Resultados esperados
  • Explicación del código
  • Syscalls de Linux utilizados
  • Depuración con strace
  • Referencias

El problema

Resolución de rutas en Linux

Cuando el kernel resuelve una ruta como /a/b/c/file, lo hace componente por componente. En cada paso, si el componente es un symlink, el kernel lo sigue automáticamente. Esta resolución no es atómica — el sistema de archivos puede cambiar entre dos pasos.

TOCTOU en el kubelet

El kubelet de Kubernetes hacía exactamente este patrón vulnerable:

1. CHECK : lstat(subPath) → "es un directorio, es seguro"
     ↕ VENTANA DE RACE — un proceso en el contenedor cambia el directorio por un symlink
2. USE   : mount(subPath) → sigue el symlink, monta el sistema de archivos host

Entre la verificación y el mount, un proceso malicioso en el contenedor podía reemplazar el subPath por un symlink hacia / del host, obteniendo así acceso completo al sistema de archivos host.

La solución: openat2(2)

La syscall openat2 (kernel 5.6+) resuelve la ruta y abre el archivo de manera atómica, con restricciones:

FlagEfecto
RESOLVE_NO_SYMLINKSRechaza seguir cualquier symlink → devuelve ELOOP
RESOLVE_BENEATHRechaza salir del directorio base → devuelve EXDEV
RESOLVE_IN_ROOTTrata el dirfd como la raíz del sistema de archivos
RESOLVE_NO_XDEVRechaza atravesar puntos de montaje

Con openat2, no hay ventana TOCTOU: si aparece un symlink durante la resolución, la syscall falla inmediatamente.


Cómo funciona el exploit

Dos hilos cooperan para explotar la ventana TOCTOU:

workdir/
├── legit_dir/
│   └── secret.txt  → contiene "LEGIT"
├── symlink_target/
│   └── secret.txt  → contiene "PWNED"
└── target/          → intercambiado entre directorio real y symlink

Hilo Racer

Corre en un bucle ultrarrápido e intercambia atómicamente target/ entre dos estados mediante renameat2(RENAME_EXCHANGE):

  • Estado A : target/ es un directorio real (contiene secret.txt = "LEGIT")
  • Estado B : target/ es un symlink hacia symlink_target/ (contiene secret.txt = "PWNED")

Una sola syscall por intercambio = ventana de race máxima.

Hilo Victim (simula el kubelet)

Reproduce el patrón vulnerable del kubelet:

  1. fstatat("target", AT_SYMLINK_NOFOLLOW) — verifica que es un directorio
  2. Pausa opcional (simula la latencia del kubelet entre check y use)
  3. openat(dirfd, "target/secret.txt", O_RDONLY) — abre el archivo
  4. read() — lee el contenido
  5. Si contenido == "PWNED" → race win (el symlink fue seguido)
  6. Si contenido == "LEGIT" → race loss (era el directorio real)

En modo protegido (--use-openat2), el paso 3 usa openat2 con RESOLVE_NO_SYMLINKS | RESOLVE_BENEATH. Si hay un symlink, el kernel devuelve ELOOP en lugar de seguirlo.


Arquitectura del código

src/
├── main.zig       # Punto de entrada: parsea CLI, crea el estado compartido, lanza los
│                  # hilos, mide el tiempo, muestra los resultados
│
├── racer.zig      # Hilo racer: prepara el estado inicial (target → symlink),
│                  # luego buclea con renameat2(RENAME_EXCHANGE) para intercambiar
│                  # target/ y legit_dir/ continuamente
│
├── victim.zig     # Hilo victim: buclea N iteraciones de fstatat → delay →
│                  # openat/openat2 → read → compara "LEGIT" vs "PWNED"
│
├── setup.zig      # Crea el árbol de prueba: workdir/, legit_dir/,
│                  # symlink_target/, target/ con los archivos centinela
│
├── syscalls.zig   # Constantes y wrappers para syscalls raw:
│                  # - struct open_how (UAPI del kernel, 3×u64)
│                  # - flags RESOLVE_*
│                  # - RENAME_EXCHANGE (= 2)
│                  # - openat2() mediante linux.syscall4(.openat2, ...)
│                  # - rename_exchange() mediante linux.renameat2()
│                  # - Helpers: is_err(), to_errno(), to_fd()
│
└── stats.zig      # Contadores atómicos lock-free (std.atomic.Value(u64))
                   # para wins, losses, errors, eloop + salida formateada

Todas las syscalls se invocan mediante std.os.linux.* directamente (sin wrappers de std.fs o std.posix). La única syscall sin wrapper en la stdlib de Zig 0.15 es openat2, que se llama mediante linux.syscall4(.openat2, ...) con una estructura open_how definida manualmente a partir de los headers UAPI del kernel.


Requisitos previos

HerramientaVersiónPor qué
Zig0.15.xCompilador + compilación cruzada
DockercualquieraPara ejecutar el binario Linux en Mac
colimacualquieraRuntime Docker en macOS (o Docker Desktop)

El binario se compila en estático (musl libc) y funciona en cualquier Linux sin dependencias.

Kernel mínimo:

  • 3.15+ para renameat2(RENAME_EXCHANGE)
  • 5.6+ para openat2 con RESOLVE_* (necesario solo para --use-openat2)

Docker Desktop y colima en Mac usan un kernel 6.x — todo está soportado.


Build

Compilación cruzada Mac → Linux

# ARM64 (Mac M1/M2/M3 → Docker colima / EC2 ARM)
zig build -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe

# x86_64 (para nodos x86)
zig build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe

El binario está en zig-out/bin/race-exploit.

$ file zig-out/bin/race-exploit
ELF 64-bit LSB executable, ARM aarch64, statically linked

Uso

Prueba local con Docker

# Iniciar el runtime Docker (si macOS)
colima start

# Ejecutar el exploit (modo vulnerable por defecto)
docker run --rm -v $(pwd)/zig-out/bin:/app alpine /app/race-exploit --iterations 10000

Comparación openat vs openat2

# Modo vulnerable (openat) — la race funciona
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 10000

# Modo protegido (openat2) — la race está bloqueada
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 10000 --use-openat2

O con el script que lanza ambos:

./scripts/run_in_docker.sh --iterations 10000

Jugar con el retardo TOCTOU

El parámetro --delay-us añade un retardo entre el lstat (check) y el openat (use). Cuanto más largo sea el retardo, más ancha es la ventana TOCTOU, y mayor es el win rate:

# Sin retardo — win rate ~25%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000

# 10µs — win rate ~35%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000 --delay-us 10

# 100µs — win rate ~50%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000 --delay-us 100
Descargar herramienta