
تحليل متعمق واستغلال لـ CVE-2019-13272، وهي ثغرة تصعيد صلاحيات في نواة لينكس عبر ptrace. يتضمن شرحًا للكود وسيناريو الاستغلال.
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);
هناك طريقتان لإنشاء علاقة تعقب (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)