Reproduction et analyse de la cause racine de CVE-2023-32233, une vulnérabilité use-after-free dans nf_tables du noyau Linux permettant une élévation de privilèges locale, avec PoC et notes d'exploitation.
Ce qu'est ce dépôt : ma reproduction pratique et mon étude de CVE-2023-32233, un use-after-free dans le sous-système Netfilter nf_tables du noyau Linux qui permet une élévation de privilèges locale, divulgué publiquement en mai 2023 par Patryk Sondej et Piotr Krysiuk.
Crédit : le PoC (exploit.c) et l'analyse originale sont de
@Liuk3r. Mon travail dans ce dépôt
consiste à reproduire l'exploit dans mon propre laboratoire (Ubuntu 23.04, noyau 6.2.0-20)
et à documenter la cause racine et la chaîne d'exploitation ci-dessous. Tous les tests
ont été effectués sur des machines de laboratoire dédiées.
nf_tables traite les mises à jour de configuration comme un lot atomique. La validation de chaque opération par rapport aux changements d'état des opérations précédentes dans le même lot est insuffisante. Concrètement :
nft_rule contenant une expression lookup sur un
nft_set anonyme qui contient quelques éléments.NFT_MSG_DELRULE — supprime la règle, ce qui supprime implicitement
l'expression lookup et le nft_set anonyme ;NFT_MSG_DELSETELEM — supprime un élément du set anonyme déjà supprimé.nf_tables_commit_release() met en file d'attente les ressources
sur nf_tables_destroy_list, traitées plus tard par
nf_tables_trans_destroy_work() :
d'abord nft_commit_release() → nf_tables_rule_destroy() →
nft_lookup_destroy() → nft_set_destroy() → kvfree() libère le
nft_set ;
ensuite, pour NFT_MSG_DELSETELEM, nf_tables_set_elem_destroy() appelle
nft_set_elem_ext() qui déréférence le nft_set libéré :
static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,
void *elem)
{
return elem + set->ops->elemsize;
}
Si set->ops->elemsize est corrompu, un emplacement mémoire choisi par l'attaquant
est interprété comme un nft_set_ext — la primitive sur laquelle tout le reste se construit.
nf_tables_trans_destroy_work() s'exécutant sur un
thread worker en arrière-plan : insérer une grande opération de destruction de set comme délai
contrôlé, occuper les autres CPU, et réallouer le chunk nft_set libéré
depuis le même CPU avec un nft_set d'un type différent
(elemsize différent) → confusion de type.nft_set_ext corrompus avec des offsets hors limites pour que
nf_tables_set_elem_destroy() parcoure les chunks adjacents comme une liste de
nft_expr à détruire.nft_log avec un NFTA_LOG_PREFIX contrôlé ;
nft_log_destroy() libère priv->prefix, donnant une primitive d'allocation
chevauchante dans kmalloc-{8..192} (limitée par l'octet NULL au début).nft_object->udata pour lever la restriction de l'octet NULL
sur la lecture pendante.nft_dynset pour deux types d'expressions à état :
nft_counter — fuite de nft_counter_ops → base de nf_tables.ko
(contourne KASLR pour le module) ;nft_quota — le pointeur consumed donne une lecture arbitraire
(NFT_MSG_GETSETELEM → nft_quota_do_dump() → NFTA_QUOTA_CONSUMED)
et une écriture arbitraire (nft_overquota() ajoute skb->len à
*priv->consumed sur le trafic loopback).modprobe_path ("/sbin/modprobe" → "//tmp/modprobe") →
exécution d'un processus root avec un contenu contrôlé par l'attaquant.Notamment, les primitives choisies évitent tout ce que CFI bloquerait — aucun détournement d'appel indirect n'est nécessaire à aucune étape.
SLAB_FREELIST_HARDENED), INIT_ON_FREE, et restrictions de réutilisation
du slab par CPU attaquent tous l'étape 1 ; CFI (là où présent) contraint l'étape 3+.nf_tables
contenant DELRULE+DELSETELEM sur des sets anonymes ; triage de crash
pour nft_set_elem_ext / nf_tables_trans_destroy_work dans la pile.nftables nécessite CAP_NET_ADMIN, mais
les espaces de noms utilisateur non privilégiés l'accordent — c'est pourquoi
kernel.unprivileged_userns_clone=0 est un levier de durcissement significatif.Testé sous Ubuntu 23.04 (Lunar Lobster), noyau 6.2.0-20-generic.
sudo apt install gcc libmnl-dev libnftnl-dev
gcc -Wall -o exploit exploit.c -lmnl -lnftnl
Le profil intégré cible les noyaux binaires Ubuntu 23.04
(linux-image-6.2.0-20-generic 6.2.0-20.20). Pour tester d'autres noyaux,
extraire les symboles dans un fichier profile :
modprobe nf_tables
egrep ' (nft_counter_ops|nft_counter_destroy|free_percpu|modprobe_path)(\s|$)' /proc/kallsyms > profile
La disposition du code machine de nft_counter_destroy() varie selon le compilateur et
les options ; voir ORIGIN.md pour la référence complète des paramètres
(nft_counter_destroy_call_offset/mask/check) et les réglages de course
(race_lead_sleep etc.). La probabilité de succès rapportée est ≥80% sur des systèmes
Intel bare-metal au repos ; certaines microarchitectures (par ex. Alder Lake) nécessitent
un réglage supplémentaire.
Le PoC laisse le noyau dans un état instable avec de la mémoire corrompue après une exécution réussie. Ne tester que sur un système dédié et jetable ou un snapshot de VM — jamais sur quoi que ce soit contenant des données qui vous importent.
exploit.c — code source du PoC (par @Liuk3r)ORIGIN.md — analyse originale de la vulnérabilité et de l'exploitation (par Liuk3r)README.md — ce fichier : reproduction + mes notes d'analyseRecherche menée à des fins défensives/éducatives dans un environnement de laboratoire isolé.