Server Side Template Injection (SSTI)

السلام عليكم ورحمة الله وبركاته

 

أمور يجب عليك معرفتها قبل قراءة هذه المقالة:

-        معرفة ضرورية: ثغرة SSTI (Server-Side Template Injection) ، أرشح لك هذه المقالة العربية

-        ليست ضرورية لكن جيدة: معرفة بسيطة في Docker حيث إن المعمل الذي سنقوم بالتعامل معه عبارة عن Docker Container ، أرشح لك وبشدة هذه السلسلة العربية

 

 

في هذه المقالة لن نتطرق لشرح الثغرة نفسها، إنما سنحاول التركيز على الجانب التطبيقي لهذة الثغرة. تحديدًا سنقوم بنقاش هذه النقاط:

-        تثبيت التطبيق المُصاب

-        إكتشاف الثغرة وتحديد الـ parameter المُصاب

-        إكتشاف نوع الـTemplate Engine  المستخدم في تطبيق الويب

-        إستغلال الثغرة

التطبيق المُصاب الذي سيتم الشرح عليه تجده هنا

 

لنبدأ بسم الله

تثبيت التطبيق المُصاب

بعد تثبيت Docker في جهازك قم بتنفيذ التعليمة التالية والتي ستقوم بعمل fetch لـلـ docker container

sudo docker pull dockerbucket/ssti_env

والتعليمة التالية ستقوم بتشغيل الـ Docker container  وبدء التطبيق على المنفذ 60

sudo docker run -p 60:60 dockerbucket/ssti_env python /root/vulncode.py

 

إكتشاف الثغرة وتحديد الـ parameter المُصاب

بعد تشغيل التطبيق سنجد أن واجهة التطبيق تحتوي على خانة بحث

كما ذكر @u0pattern_cs في مقالته بامكاننا اتباع المنهجية المذكورة في الصورة التالية لاكتشاف ثغرة الـ SSTI

سنقوم دائمًا بتتبع السهم الأخضر، إلا في حالة الفشل سنقوم باتباع السهم الأحمر

سنرى أن أول payload يفترض أن نبدأ بها هي

${3*2}

سنحاول تمرير عملية حسابية بسيطة 2 * 3 لنختبر هل سيقوم التطبيق بتنفيذها؟ في حال كان التطبيق مُصاب سيقوم بتنفيذ العملية.

الـ ${ }  هي لملائمة الـ Syntax فقط

وقبل تمرير الـ request للخادم لو قمنا بعمل interception  سنجد أننا نقوم بالحقن في المتغير username

 

بعد ارسال الطلب للخادم سنجد الـ response  الآتي :

 

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

لننتقل للاختبار التالي، سنقوم الآن بتمرير الـ payload التالية

 

بعد ارسال الطلب للخادم سنجد الـ response  الآتي :

ممتاز!! قام الخادم باجراء العملية الحسابية ولتأكيد وجود الثغرة لا يزال أمامنا حالة اختبار اخرى وهي تمرير الـ payload التالية

 

بعد تمرير الطلب سنجد الرد الآتي من الخادم

من النتيجة أعلاه نستطيع التأكيد أن التطبيق مُصاب بثغرة SSTI حيث أن التطبيق لا يزال يقوم بتنفيذ التعليمات التي نقوم بتمريرها  ( التطبيق قام بطباعة 2 ثلاثة مرات )

وتحديدًا الـ parameter  المُصاب هو username

 

إكتشاف نوع الـTemplate Engine  المستخدم في تطبيق الويب

بعد أن تأكدنا أن التطبيق مُصاب، نحن الآن بصدد تحديد نوع الـ Template Engine المُستخدم في تطبيق الويب، ونطاق الاختبار  ( Test Cases) أصبح كالآتي :

تقترح علينا المنهجية التي نتبعها أن التطبيق قد يكون Jinja2 ( خاص بـ Python ) أو Twig ( خاص بـ PHP )

سنبدأ أولًا في اختبار حالة Jinja2 ولأنه مخصص للغة بايثون سنقوم بتمرير الـ payload  التالية :

يبدو أن الأمور بدأت تتعقد قليلًا الأن ، لنتوقف لبضع دقائق ونحاول نفهم ما الذي تقوم هذه الـ payload بتنفيذه،

وحتى نفهم هذا نحن بحاجة للعودة الى الـ Python Environment والتعامل معاها مباشرة،

الصورة التالية توضح أني قمت ببدء interactive shell مع الـ docker container المثبت به التطبيق، ومن ثم قمت ببدء python2 console

الأن نحن في داخل بيئة بايثون ، لنبدأ بتجزئة الـ payload وتنفيذ كل جزء على حدى، حتى نفهم العائد النهائي منها

-        ‘’  : عبارة عن string

-        __class__  : الـ __class__ عبارة عن attribute تقوم باعادة اسم الـ class الذي ينتمي له الـ object وفي حالتنا العائد سيكون str class  كما نرى

-        __mro__  : الـ __mro__ ستقوم باعادة قائمة الـ inherited classes لهذا الـ object وفي حالتنا نرى أننا قمنا بتمرير الـ index رقم 2 ، والغرض من ذلك أننا نريد استرجاع عنصر معين من هذه القائمة ( وهو object class )

-        __subclasses__()  : الـ __subclassess__()  عبارة عن method ستقوم بارجاع قائمة بالـ classes التي تم عمل loaded لها في بيئة التطبيق الحالية كما نرى في الصورة التالية

اذًا ما الذي تقوم بفعله الـ payload كاملة؟ بشكل مُختصر جدًا تقوم بعمل dump  للـ classes names التي نستطيع الوصول لها ضمن بيئة التطبيق

ولماذا إذًا كل هذا التفصيل والتعقيد ؟ سنرى لاحقًا أن فهم آلية عمل مثل هذه الـ payload سيساعدنا في بناء payloads أخرى معقدة لحالات معينة،

اضافة الى أنه قد تختلف الـ classes التي نستطيع الوصول لها من تطبيق الى تطبيق،

ففهم كيف نتحرك ضمن الـ Python Inheritance Hierarchy  سيساعدنا في الوصول الى أي class  ومن ثم نداء دواله الخاصة

قبل أن نستكمل أود فقط الاشارة بأن هذه الـ payload  مخصصة فقط لـ python2 وفي حال أردت استخراج أسماء الـ classes في بيئة python3 سنمرر الـ index رقم 1 للـ __mro__ كالآتي :

بعد فهمنا للـ payload  قمنا بتمريرها للتطبيق كالآتي ، ونلاحظ أننا نجحنا في عمل dumping للـ classes names

نلاحظ بعد استخراجنا لقائمة الـ classes أنه يوجد من ضمنها classes خاصة بـ Jinja2 ..

اذًا في هذه المرحلة نستطيع القول الأن أن الـ Template Engine الذي نتعامل معه هو Jinja2 وليس Twig

 

الأن لنستكمل آخر جزء في هذه المقالة

إستغلال الثغرة

قراءة /etc/passwd

نستطيع استخدام دالة read() من File class في بايثون حتى نقرأ ملف ما، الـ payload  التي تقوم بالوصول للـ File class كالآتي

لاحظ أننا مررنا الـ index  رقم 40 لدالة subclasses .. هذا الـ index  خاص بـ File class ( لا أعتقد أنه قد يكون رقم ثابت في جميع التطبيقات، لذلك عملية استخراج أسماء الكلاسات مهمة)

كيف حصلنا على الـ index  ؟ من خلال قائمة الـ classes التي عملنا لها dump  .. وبعد ذلك حللنا المخرج وعرفنا الـ index  الخاص بالكلاس

والـ payload  التي تمكننا من قراءة ملف الـ /etc/passwd  كالآتي

بعد تمرير هذه الـ payload  للتطبيق نجد أننا استطعنا قراءة الملف بنجاح

تنفيذ ls

في بايثون حتى نستطيع تنفيذ bash commands نستعين بمكتبة os تحديدًا دالة popen() (هذه طريقة من ضمن طرق أخرى متاحة)

لكن بعد مراجعة قائمة الـ classes  التي استخرجناها من التطبيق لم نجد هذه المكتبة .. فكيف سنصل لها ؟

ضمن بيئة بايثون الطبيعية عادةً نقوم باستخدام import ونستدعي المكتبة .. فهل بالامكان تطبيق نفس هذه الفكرة هُنا ؟

الاجابة نعم .. وبتفصيل أكثر اطلع على هذا المشروع الذي قام بشرحه أحد الباحثين لطلابه ( المشروع بالمناسبة يقوم بحل نفس التطبيق المُصاب)

بشكل مختصر جدًا الفكرة التي اقترحها الباحث هي استخدام دالة catch_warnings()  حتى نصل الى الـ import statement  ومن ثم نستطيع عمل import لمكتبة os

الـ payload  التي تلخص الخطوات التي اقترحها الباحث كالآتي

ولو قمنا بتمريرها سنجد أننا استطعنا تنفيذ الـ ls

Reverse Shell

بامكاننا استخدام نفس الطريقة السابقة لكن عوضًا عن تمرير ls لدالة popen() سنقوم بتمرير هذا الـ one-line python script

وستكون الـ payload  النهائية كالآتي

وبعد تمريرها سنحصل على الـ shell session


 

ختامًا، أود الإشارة بأن هذه المقالة تمّت كتابتها خلال دراسة هذه المواضيع، فكل ما تم ذكره هنا قد يحتمل الخطأ، لكن بالإمكان العودة إلى المراجع التي إستندت عليها هذه المقالة

وإن أصبت فمن توفيق الله وحده، وإن أخطأت فمن نفسي والشيطان

 

 

المراجع:

مقالات:

-        https://3alam.pro/1337r00t/articles/ssti

-        https://bowneconsultingcontent.com/pub/EH/proj/ED105.htm

دورات:

-        AWAE, Offensive Security