PHP Type Juggling Vulnerability
السلام عليكم ورحمة الله وبركاته
أمور يجب عليك معرفتها قبل قراءة هذه المقالة:
- معرفة ضرورية: لغة PHP
- ليست ضرورية لكن جيدة: معرفة بسيطة في Docker حيث إن المعمل الذي سنقوم بالتعامل معه عبارة عن Docker Container ، أرشح لك وبشدة هذه السلسلة العربية
في هذه المقالة سنتطرق لشرح هذه النقاط :
- مفهوم الـ Type Juggling في لغة PHP
- ثغرة PHP Type Juggling
- إستغلال الثغرة
مفهوم الـ Type Juggling في لغة PHP
لغة PHP تعتبر Dynamically Typed Language مما يعني تحديد وتعريف نوع المتغيرات ( variables ) غير مطلوب ومدعوم في هذه اللغة أساسًا ، انما يتم تحديد نوع المتغير بحسب القيمة التي يحملها إن صح التعبير، أيضًا بالامكان تغيير نوع هذا المتغير خلال سير البرنامج عبر تخزين قيمة أخرى من نوع آخر
ومن الممكن ايضًا أن يتغيّر نوع المتغير خلال عمليات المقارنة ، على سبيل المثال، ماذا سيحدث إن قمنا بمقارنة رقم (int) مع نص (string) ؟ لمثل هذه الحالات يوجد في لغة PHP مفهوم يسمى Type Juggling
تقوم اللغة باتباع قوانين الـ Type Juggling الخاصة بها في حال عملية المقارنة بين متغيرين من نوعين مختلفين ، وبحسب الحالة، ستقوم اللغة بتحويل نوع أحد المتغيرات الى النوع الآخر ..
الآن السؤال المهم .. هل دائمًا ستقوم اللغة بالاجتهاد ومحاولة الموازنة بين أنواع المتغيرات خلال عمليات المقارنة ؟ الاجابة المُختصرة لا .. يوجد حالات مُعينة ستقوم لغة PHP بالعودة لقوانين الـ Type Juggling الخاصة بها ومحاولة التحويل والتقريب بين الانواع .. وفي حالات أخرى ستقوم اللغة بعمليات المقارنة فقط في حال كانت المتغيرات من نفس النوع .. وهذا يقودنا الى مفهومين آخرين في لغة PHP
Loose Comparison (==) :
هذه هي الحالة الأولى في مثالنا السابق، في هذا النوع من انواع المقارنات ستتدخل لغة PHP وتطبّق قوانين الـ Type Juggling اذا اختلفت انواع المتغيرات التي يتم مقارنتها
في هذا النوع نستخدم التعليمة == لاختبار المساواة ونستخدم != أو <>لاختبار عدم المساواة
Strict Comparison (===) :
هذا النوع هو الحالة الثانية من المثال السابق ، يتم استخدام التعليمة === لمقارنة المساواة والتعليمة !== لمقارنة عدم المساواة .. ولن تتم عمليات المقارنة الا اذا كان كِلا المتغيرين من نفس النوع
ثغرة PHP Type Juggling
بعد المقدمة السابقة، لننتقل الآن لفهم متى وأين تحدث المشكلة ؟
وأفضل طريقة لنفهم هي أن نختبر ماسبق داخل بيئة PHP
في الكود أعلاه قمنا بمقارنة رقم 3 مع النص "3" و نلاحظ أن ناتج العملية هي المساواة equal
فمالذي حدث هنا ؟
لغة PHP طبّقت مفهوم الـ Type Juggling وقامت بتحويل النص "3" الى رقم وبالتالي العملية ستصبح 3 == 3 وبالطبع ناتج العملية سيكون صحيح
وللتأكّد سنقوم باستبدال الرقم برقم آخر، ولو قمنا بتنفيذ الكود سنلاحظ أن ناتج العملية هو اللامساواة، بالطبع 3 لا تساوي 4
في المثال التالي قمنا باضافة جملة نصية فعلية للمتغير الأول لنلاحظ كيف ستتم العملية .. فالآن نحن نقارن متغيرين، الأول عبارة عن رقم وحروف والثاني عبارة عن رقم فقط
نلاحظ أن ناتج المقارنة هو المساواة .. فماذا حدث هنا ؟
قامت PHP بتحويل “3test” الى 3
أي أنه تم النظر الى الجزء الأول من المتغير 3test وهو رقم 3 وتم الغاء النص test
وبالتالي كانت عملية المقارنة النهائية كالآتي : 3==3
والناتج بالطبع سيكون صحيح
سنأخذ مثال آخر يحتوي على مقارنة نص فقط مع رقم
ونلاحظ أن الناتج هو اللامساواة
لنستكمل مثال آخر لكن سنقوم بتغيير الرقم 3 الى 0 ، أي ستكون المقارنة الرقم 0 مع نص
على خلاف المثال السابق النتيجة هنا كانت هي المساواة على الرغم بأنه في كلا المثالين قمنا بمقارنة رقم ونص .. فكيف تعاملت PHP في هذه الحالة ؟
الذي حدث باختصار انه عندما تقوم PHP بمقارنة نص مع رقم، ستنظر أولًا هل يبدأ هذا النص برقم ؟ في حال تحقق هذا ستقوم PHP باخذ هذا الرقم واجراء عملية المقارنة ..
وفي حال كان النص لا يحتوي على أي رقم ، ستقوم PHP بتحويل كامل النص الى القيمة صفر ، وتقارنه مع الرقم المقابل
بعد أن فهمنا الـ Type Juggling بشكل عملي .. متى اذًا تحدث المشكلة ؟
تصوّر معي الكود التالي لعملية الـ authentication
في الكود أعلاه سيتم استقبال كلمة المرور من POST request ومقارنتها بكلمة المرور “Admin_Password” ونلاحظ أنه تم استخدام == في عملية المقارنة ، أي أنها loose comparison وسيتم تطبيق الـ Type Juggling هنا
فلو قمنا بتمرير صفر كرقم سيكون ناتج العملية صحيح بالتالي نستطيع تسجيل الدخول
هذه احدى الحالات التي يتم استخدام الـ loose comparison في المكان الخاطئ بالتالي يكون التطبيق مُعرّض لثغرة PHP Type Juggling
يوجد حالة أخرى شيقة جدًا! وهي كالآتي:
نلاحظ أننا قمنا بمقارنة نص مع الرقم صفر والنتيجة كانت مساواة
لو قمنا بالتدقيق سنجد أن النص في صورة exponential notation
بالتالي ناتج المقارنة بين المتغيرين سيكون المساواة..
الجدير بالذكر هنا أنه يوجد مايسمى بالـ Magic Hashes
وهي عبارة عن قيم Hashes تأخذ هذا التمثيل (Regex) :
فاذا كان لدينا نص بعد عملية الـ Hashing سيكون في هذا الشكل وتمت مقارنته مع القيمة صفر فسيكون الناتج صحيح ..
- ملاحظة: اصدار PHP في الأمثلة السابقة هو 5
في الجزء التالي من المقالة سنرى مثال فعلي على هذه الحالة وطريقة استغلالها
إستغلال الثغرة
المعمل الخاص بهذه الثغرة هو تطبيق ATutor الاصدار 2.2.1
قمت سابقًا بتثبيت هذا التطبيق على docker image بالامكان الوصول لها واتباع تعليمات التثبيت من هنا
لن نخوض في خطوات ايجاد الثغرة انما سنقوم بالتركيز على الثغرة نفسها،
فالتطبيق مصاب بثغرة PHP Type Juggling وتحديدًا في ملف /atutor/confirm.php
لو قمنا بالاطلاع على الكود الخاص بهذا الملف سنجد الـ loose comparison في السطر 36
بشكل مختصر الكود أعلاه يقوم بالمقارنة بين قيمة المتغير code والمتغير m ، في حال تساوت القيمتين سيتم تنفيذ الكود التالي بدءًا من السطر رقم 37 ، والذي سيقوم بتحديث الايميل في قاعد البيانات
هدفنا الآن هو تحقّق المساواة في السطر 36 بالتالي نستطيع تحديث الايميل الخاص بالمستخدم في قاعدة البيانات
وحتى نحقق هذا الهدف يجب علينا التحكم في جميع اطراف المقارنة
بعد تحليل الكود والتطبيق وجدنا أن المتغير m هو متغير تحت تحكّمنا ونستطيع تمريره للتطبيق عبر GET request بينما المتغير code هو متغير يتم انشاء قيمته في داخل التطبيق، تحديدًا في السطر رقم 34
نجد أن المتغير code يتم انشاء قيمته كالآتي :
1. جمع (concatenation) قيم المتغيرات الآتية : Id & e & creation_date
2. حساب الـ Hash عبر تطبيق دالة الـ md5 على ناتج الـ concatenation
3. أخذ الـ 10 حروف الأولى من ناتج الـ md5 ( تطبيق دالة substr )
في الخطوة رقم 1 نستطيع التحكم بالمتغير id والذي يمثل الـ id الخاص بالمستخدم الذي نريد تحديث الايميل الخاص به
ونستطيع ايضًا التحكم بالمتغير e والذي يمثل قيمة الايميل الجديد ..
كلا المتغيرين id و e نستطيع تمريرهم عبر GET request مع المتغير m ، يتبقى لدينا فقط متغير واحد يلعب دور في انشاء قيمة المتغير code وهي قيمة الـ creation_date التي يتم جلبها من قاعدة البيانات
فهل هذا يعني أنه لا يمكننا استغلال الثغرة ؟ الاجابة لا
سنقوم بعمل Brute-forcing حتى نصل الى حالة يصبح فيها شرط المقارنة صحيح
وهذا هو الجزء الأساسي في الاستغلال
لنسترجع الحالة الأخيرة التي قمنا في شرحها في الجزء السابق،
عرفنا أنه يصبح ناتج المقارنة صحيح اذا كان الطرف الأول قيمته صفر ( في حالتنا هذه سيكون المتغير m والذي نستطيع التحكم به بشكل كلي) والطرف الثاني يحمل قيمة تمثل هذا الـ regex
الطرف الثاني في المعادلة سيكون المتغير code والذي سنقوم بعمل brute-force عليه حتى نصل لحالة تصبح قيمة هذا المتغير في شكل الـ regex الذي نريد الوصول اليه ..
وفي حال نجحنا في عملية الـ brute-forcing سيكون شرط المقارنة صحيح بالتالي سنستطيع تحديث الايميل في التطبيق
قمت بكتابة سكربت يحاكي العملية هذه كاملة .. الدوال المسؤولة عن عملية الـ brute-forcing واستغلال ثغرة الـ Type Juggling هي كالآتي :
وبعد تشغيل السكربت نرى أننا وجدنا القيمة التي تحقّق الشرط
ولو قمنا بالدخول الى التطبيق سنجد أن الايميل بالفعل تم تحديث قيمته بالقيمة التي قمنا بايجادها عبر الـ Brute-forcing
ملاحظة أخيرة:
أود الاشارة بانه في السكربت قمت باستغلال ثغرة اخرى وهي Blind SQLi لقراءة قيمة الـ creation_date ، هذه الخطوة ليست ضرورية وبالامكان بناء استغلال صحيح بدونها حيث أن عملية الـ calculation ستتم في طرف الـ Server ، لكن في حالتي قمت فقط بجلب هذه القيمة لتكرار عملية المقارنة في طرفي ومراقبة النتائج
ختامًا، أود الإشارة بأن هذه المقالة تمّت كتابتها خلال دراسة هذه المواضيع، فكل ما تم ذكره هنا قد يحتمل الخطأ، لكن بالإمكان العودة إلى المراجع التي إستندت عليها هذه المقالة
وإن أصبت فمن توفيق الله وحده، وإن أخطأت فمن نفسي والشيطان
المراجع:
مقالات:
- https://www.alertlogic.com/blog/writing-exploits-for-exotic-bug-classes-php-type-juggling-d58/
- https://www.php.net/manual/en/language.operators.comparison.php
- https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09
- https://raz0r.name/vulnerabilities/simple-machines-forum/
دورات:
- AWAE, Offensive Security