Tuesday, June 22, 2021

DIRTY COW ثغرة الـ

 


"يمكن لأي مستخدم أن يصبح متحكمًا بشكل كامل في أقل من 5 ثوانٍ عند اختباره الثغرة ، بشكل موثوق للغاية! أشياء مخيفة

 Phil Oster


عند مذاكرتي لمنهج الـ CyAs+ مررت بنقطة مهمة جدًا في الفصل الخامس من" Analyzing Vulnerability Scans" للمؤلفين Mike Chapple و David Seidl ، و هي الـ "privilege escalation" او ترجمتها حرفيًا " تصعيد الصلاحيات" : مُصنفة كثغرات أمنية او اخطاء برمجية موجوده في برمجه النظام او في بنيته للحصول على صلاحيات اكبر من تلك التي مُنحت الى المستخدم والتي يفترض ان لا يتجاوزها (عادة الـ local user) و لا يستطيع تنفيذ بعض الامور خارج صلاحياته.

تم ذكر مثال جدًا ملفت للانتباه و هي هجمة البقرة القذرة او الـ "Dirty Cow"! 



صورة لشعار الثغرة

مسمى غريب لثغرة امنية صحيح؟

مالفت انتباهي ليست الثغرة بحد ذاتها بل سبب التسمية! مالذي دفع مكتشفها لتسميتها بهذا الاسم؟ ماهو الضرر التي احدثته هذه الثغرة لتنال هذا اللقب بجدارة؟!

بعد بحث مطول و قراءة في ورقتين علميتين (يبدو انهما الوحيدتين من أسهبت في هذه الثغرة)، قد يبدو ان المكتشف لديه حس الفكاهة، فمسمى الهجمة لا ترتبط مباشرة بالبقرة و لكن اختصار الثغرة شَكَل كلمة (COW) و التي تعني البقرة، و لأن الناتج منها كان ضرره عالي على الأجهزة المبنية على نظام لينكس فلربما ازعج المكتشف فأسبقها بالـ "dirty".!

لنتعرف بشكل مفصل عن الثغرة في هذه المقالة.


ماهي الـ (Dirty Cow؟

هي عبارة عن ثغرة امنية موجودة على (Linux Kernel) و تأثر على أي نظام معتمد على Linux بما فيها أنظمة Android.
تسميتها كما ذكرنا سابقًا ،ليست حرفيًا تدل على البقرة (COW)! لا هي اختصار لـ Dirty Copy On Write ، و لأن هذا اختصارها فقد تم اعتماد شعار البقرة دلالة عليها.

أنه باستطاعة الشخص العادي الحصول على تحكم كامل في الكتابة على ذاكرة مخصصة للقراءة فقط (gain write permission in a read only memory) و هذا يتم مثلًا عن طريق الـ (root user).


شرح بطريقة بسيطة:

بمعنى ان الـ (local user) يستطيع ان يحصل على تحكم كامل في الكتابة على ملفات مخصصة للقراءة فقط (و هذه من خصائص الـ root user).

كيف يتم؟
يتم من خلال استغلاله للـ (race condition) قديمة جدًا لنُسخة عند عملية الكتابة.
اكتشفها Phil Oster  عام ٢٠١٦ في أكتوبر و تم تسجيل هذه الثغرة برمز (CVE- 2016-5195).

بشكل مُفصل :

عند فتح ملف، يتم تعيين موقعه لمساحة مخصصة في ذاكرة معينة، هنا لا يُسمح لأحد بالكتابة عليه.
فالحل المنطقي، ينبغي على الـ (Linux Kernel) ان تنسخه لموقع ذاكرة جديد قبل ما تسمح للكتابة في الملف الأساسي.

لو فرضًا احد ما لديه ملف، هذا الملف ليس له اذن كتابة (write permission) – بمعنى انه يقدر يقرأ ولا يكتب -، فالثغرة سيتم استغلالها عن طريق الـ (race condition). حيث يقوم المهاجم يقوم بالترتيب لكتابة نسخة في موقع ذاكرة جديدة، و يحاول تكرارها مرارًا، حتى اخيرًا يتم  كتابة ملف أساسي.

دعونا قليلًا نفصل في الـ (race condition):
    هو عيب أو خلل في النظام ، لم يؤخذ بعين الاعتبار عند التصميم، يظهر بطريقة غير متوقعة حين الحصول على نتيجة مختلفة حسب الترتيب الذي نُفذت به العمليات، قد يلزم إعادة تشغيل النظام بسببه. في الشكل الظاهر أمامنا تظهر لنا خطوات (race condition).

    في بيئة نظام التشغيل الطبيعية ، تعمل العمليات في (multithread) مما يعني عند اكتمال عملية واحدة تبدأ العملية التالية وتنتهي كما هو موضح في السلوك الصحيح (correct behavior).
الفرق بين العمليتين صغير جدًا ، بحيث يَظهر للمستخدمين أن هذه العمليات تعمل بشكل متوازي. و في حالات خاصة ، قد تبدأ عملية واحدة قبل اكتمال العملية السابقة. كما هو موضح بالشكل، تبدأ الـ(Task 1) و قبل ان تكتمل تبدأ الـ(Task 2) وتنتهي ثم تنتهي (Task 1).

بسبب عدم توقع هذه النتيجة ، يُفترض هُنا الـ (race condition) مما يتم استخدامها في الـ (Dirty Cow)، حيث ان المهاجم يستغلها لرفع امتيازات المستخدم و حصوله على الـ (root user) فيحصل على permission عن طريق الكتابة فوق ملف ( etc/passwd).

صورة توضيحية: Race condition


بالعودة للمقال، سيكون للمهاجم باستطاعته تمييز الذاكرة القابلة للكتابة قبل نسخها من خلال سلوك الـ (Kernel).

الملف عند فتحه سيكون بوضعية الـ (read only mode)،  الـ (root user) هو الوحيد الذي لديه إمكانية الـ كتابة عليه.

الفكرة: نحن المستخدمين العاديين نريد هذا الملف.


f=open(argv[1],O_RDONLY);
fstat(f,&st);
name=argv[1];


بعدها ننتقل إلى الكود التالي، (mmap) يستخدم لإنشاء مساحة ذاكرة معينة جديدة مخصصة للعملية الحالية. و تهدف انها تعين ملف في الذاكرة و تضع عليه علامة كمقروء فقط مع علامة بشكل علم باللون الأحمر و يرمز بـ "f" للكود التالي.

هُناك علم آخر مهم هو (Map_Private)، يقوم بإنشاء نسخة خاصة عند تعيين الكتابة (COW).


map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);

printf("mmap %zx ",(uintptr_t) map);


(mmap) لا ينسخ ملف الى الذاكرة!  
يقوم فقط بإنشاء تعيين للملف الأساسي في الـ(hard disk) لتحسين أداء نظام التشغيل. في حال لو أردنا الكتابة على ملف القراءة فقط – الذي تم تعيينه في موقع الذاكرة- فإن الـ (Linux kernel) سيأخذ نسخة منه.

النقطة المهمة التي يجب ملاحظتها، ان الـ(mmap) يُعين ملف الـ (root privileged) في مساحة الذاكرة، و ليس على الملف الأساسي. و يمكنك قراءة الملف او الكتابة على نسخه منه.

لا ينبغي تطبيق التغييرات نسخة الملف (the root) على ملف الـ (root privileged).


قبل أن نُكمل سنقوم فقط بتعريف ماهو الـ (thread
     لتوضيح فقط، هي عبارة عن مجموعة من التعليمات التي تشكل مسار مُعين لتنفيذ عملية معينة ولا تحتاج لموارد خاصة فيها لانها تستخدم موارد العملية نفسها.

يبدأ الـ(COW) بـالـ (two threads) يتم تشغيلهم بشكل متزامن حتى تنشئ (race condition) كما تم توضيحه سابقًا. في الظروف العادية، يحدث تنفيذ معين لتعليمات الـ (threads) بترتيب محدد وفي الوقت المناسب، حيث تكون كل عملية متعارضة.


pthread_create(&pth1,NULL,madviseThread,argv[1]);
pthread_create(&pth2,NULL,procselfmemThread,argv[2]) ;


يتم تقديم الـ (race condition) من خلال الـ (threads) التالية، مما يسمح في النهاية للمستخدم العادي بالحصول على امتيازات الـ (root) ( بالتحديد الإدارية).

بما اننا تحدثنا سلفًا عن الـ (two threads)، فيجب توضيح كلا منهما:

الـ (thread) الأول: 

هو الـ " madvise thread". حيث انه ينصح (Linux kernel) بإن الشخص ما بيستخدم موقع الذاكرة هذا في أي وقت قريب. الشيء هذا يتم عن طريق علم " DONTNEED “.

c+=madvise(map,100,MADV_DONTNEED);


الـ (thread) الثاني:

هو الـ "proctselfmemthread". حيث انه يكون من ضمن العملية، يفتح ملف لموارد الذاكرة (opens /proc/self/mem)، التي تسمح لعملية معينة (proc/self) بفتح موقع الذاكرة الافتراضي المعين كملف (proc/self/mem). 
هذا يسمح للعملية بقراءة ذاكرتها من خلال قراءة هذا الملف.


من خلال الكود التالي، يسعى هذا الاستغلال بشكل متكرر الكتابة على هذا الملف في (loop). يقوم السطر الأول بإجراء بحث الى بداية الملف المرتبط بتعيين الذاكرة، و يقوم السطر الثاني بالكتابة بناء على الـ (the program argument)  التي نمررها له.

lseek(f,(uintptr_t) map,SEEK_SET);
c+=write(f,str,strlen(str));

هذا يقوم بتشغيل (trigger) الـ (Linux kernel) و ينفذ لنا طريقة الـ (Copy On Write) لإنشاء نسخة من هذه الذاكرة و كتابة التغييرات على النسخة و ليس على الملف الأساسي للذاكرة الأساسية.


من خلال تكرار الـ(two threads) في (loop) ، سنرسل رسائل غير مرغوب فيها الـ (madvise thread) (Read) و (proc / self / mem) (write) ، يتم إنشاء الـ (race condition) بحيث تخدع الـ (Linux kernel) التي تتعامل مع التوقيت والتسلسل بين هاذي العمليتين ، مما يتسبب في الكتابة على الملف الفعلي في الـ (hard disk) - وهو كما أسلفنا ملف للقراءة فقط لمستخدم لا يتمتع بامتيازات-.

ستسمح الثغرة في الـ (Linux kernel) بالحصول على الـ (root privilegs) عن طريق استغلالها.



  • كيف تم انتشاره وتحديده:

        يدعي Oester أن هذا الاستغلال المعين الذي تم تحميله على نظامه قد تم تجميعه مع إصدار GCC 4.8.5 الصادر في 2015/06/23.


  • حل المشكلة:
      تم تطوير الـ (signatures) في مكافحة الفيروسات التي تكتشف COW و لإن الهجوم معقد بحيث قد لا يُمكننا أن نفرق بين الـ (legitimateuse) والهجوم ؛ ولكن يمكن اكتشافه فقط بمقارنة حجم الـ (binary) للملف مع حجم الـ(binary) الأصلي. هذا يدل على أنه يمكن لبرامج مكافحة الفيروسات اكتشاف الهجوم وليس منعه ما لم يتم حظر الـ(binaries). 
تم حل المشكلة بإصدار (patches) و تحديث الـ (kernel).



في المقال القادم سيكون هناك اختبار و تطبيق على الثغرة.
تم بحمد الله. :)


المصادر:

No comments:

Post a Comment