php رفع الملفات

ثغرات رفع الملفات التعريف و الإستغلال

آخر تحيين: 06-06-2014

حماية ثغرات رفع الملفات سكريبت رفع الملفات


شروط رفع الملفات

إتاحة رفع الملفات ، مسألة حيوية و ضرورية في أغلب تطبيقات الويب . مع الوعي بالمخاطر التي ترافق هذه العملية من إمكانية فتح باب ثغرات الحماية على مصراعيه . و جعل موقعكم عرضة لها ، هذا ما لا يوده أي عاقل .
أين يكمن الخطر أثناء إتاحة رفع الملفات ؟
يأتينا التهديد من مصدرين :
التهديد الأول يأتي من الملف نفسه . فجميع مكوناته يمكن أن تتضمن ثغرة ، سواء تعلق الأمر بإسم الملف أو امتداده أو صيغته أو محتواه .
التهديد الثاني يأتينا من مكان تخزين الملف على الخادوم .
كمطوّرين ، يجب ألاّ تسمحوا لأي كان ، رفع ما يشاء ، على خادومكم . و يجب أن تكونوا على وعي بأن التهديد قائم و أن طرق إيجاد ثغرات رفع الملفات على خادومكم و استغلالها . مسألة سهلة و لا تتطلب كفاءات . أشهر طرق الإستغلال تكمن عبر رفع الشال إلى خادومكم . "الشال" هو عبارة عن سكريبت php ملغوم ، يتيح للمُهاجم الإستحواذ على موقعكم أو خادومكم . دون أن يكلفه ذلك أي عناء . حتّى معظم الشالات المستعملة للإختراق ، هي سكريبتات جاهزة ، موجودة بوفرة على الويب . مثلا قوموا بالبحث في محرك google عن الشال "c100.php" . و ألقوا نظرة أيضا على بعض الشالات الأخرى مثل C99 و r57 ...

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

هذا الدّرس ، هدفه ليس تلقينكم دروسا في الإختراق . بل هو تربوي ، يروم إلى تحذيركم من أجل أخذ الحيطة . سنعتمد على الطرق الأكثر استعمالا من لدن المطوّرين لحماية رفع ملفاتهم ، من خلالها سنتعرف على الثغرات المحتملة لكل طريقة و كيفية استغلالها :
سندرس مثال رفع ملفات الصّور ، لأنها تخضع لنفس شروط رفع الملفات الأخرى مع بعض الإضافات .
سنُسمّي ملف تخزين الصّور : "uploads"
بعض طرق الحماية التي سنراها :

  • التحقق من صيغة الملف : "Mime Type"
  • التحقق من محتوى الملف
  • التحقق من امتداد الملف : "Mime Extension"

التحقق من صيغة الملف : "Mime Type"

لنفترض أننا نريد إتاحة رفع الصّور فقط على موقعنا . و سنسمح بالصيغ التالية "jpg, jpeg, png, gif" .
أثناء رفع صورة بصيغة jpeg مثلا ، تظهر صيغتها في رؤوس HTTP على هذا الشكل "Content-Type: image/jpeg" .



القيمة : “image/jpeg” .هي صيغة الملف . توجدة صيغ عديدة حسب نوع الملف ، مثلا صيغة ملف php يمكن أن تكون كالتالي "text/x-php" . على هذه الصيغ ، يعتمد البعض للتأكد من صحة الملف . لذا نرى في العديد من السكريبتات على الويب . أن بعض المطورين يقومون بإنشاء إما لائحة سوداء ، تضم جميع صيغ الملفات الغير مسموح بها . أو لائحة بيضاء ، تضم أسماء الصيغ المسموح بها . ثم يقومون بمقارنة صيغة الملف الذي تم رفعه "$_FILES["milaf"]["type"]" بصيغ اللائحة البيضاء . إن كانت الصيغة مرخص لها سيتم رفع الملف .
لإجراء المقارنة ، سنستعمل دالة الجداول in_array
سيكون السكريبت على الشكل التالي :

<?php
// اللائحة البيضاء للصيغ المسموح رفعها
$allowed_mime_types = array(
    "image/gif",
    "image/jpeg",
    "image/pjpeg",
    "image/png"
);
//   نقوم بالبحث عن صيغة الملف الذي تم رفعه ، في جدول اللائحة البيضاء
if (in_array($_FILES["milaf"]["type"], $allowed_mime_types)) 
{
    // إذا كانت الصيغة متوافقة سيتم نقل الصورة و تخزينها .
    $destination = "uploads/" . basename($_FILES['milaf']['name']);
    move_uploaded_file($_FILES["milaf"]["tmp_name"], $destination);
	
    echo "تم رفع الملف بنجاح :\n";
    echo '<a href="'.$destination.'" target="_blank"> مشاهدة الملف </a> ';
} else {
   echo '<p style="color:red"> لم نتمكن من رفع الملف ، لأن الصيغة غير مسموح بها </p>';
}

echo '<pre>   هذه بعض المعلومات عن الملف المعني :<br>';
print_r($_FILES);
echo '</pre>';
?>

تبدو الطريقة صحيحة و منطقية ، إذا قام الزائر برفع صورة مثلا "image.gif" . سيتم رفع الملف بنجاح لأن الصيغة "image/gif" موجودة في اللائحة البيضاء . أمّا إذا قام برفع ملف "shell.php" سيتم رفضه ، لأن الصيغة "text/x-php" غير موجودة في اللائحة
كل شيء على ما يرام . أليس كذلك ؟
لا ، ليس كل شيء على ما يرام . يمكن تجاوز هذه الحماية بسهولة تامة ، كما ذكرت في أول الدّرس . فالصيغة تتنقل عبر رؤوس HTTP يمكن للزائر تزويرها . سيغير فقط صيغة : “text/x-php” بصيغة من صيغ الصور “image/jpeg” .
مرتكزا على سكريبتنا السابق ، للتحقق من الصيغة ، سينخدع الخادوم و سيعتبر أن الملف الذي بين يديه "shell.php" هو عبارة عن صورة ، و سيسمح برفعه . ثم سيتم قبول السكريبت و يكون المُهاجم قد ثبّت قدمه الأولى للسيطرة على الموقع . و لا يبقى له سوى عرض محتوى الملف على متصفحه ، ليبدأ عملية الإختراق و الإستحواذ .

استغلال ثغرة صيغة الملف

توجد عدة طرق لاستغلال هذه الثغرة . سأعطيطم مثالا ، باستعمال وظيفة "Live HTTP Headers" لمتصفح Mozilla FireFox ، يمكنكم إضافتها لمتصفحكم إن لم تكن بحوزتكم . تمكننا هذه الوظيفة من الإطلاع على الإستعلامات التي قام بها المتصفح و الأجوبة التي حصل عليها من الخادوم في رؤوس HTTP . كما يمكننا أيضا تعديل بعض البيانات ثم إرسالها مُجددا
الخطوات التي يمكنكم اتباعها لاستغلال الثغرة .

  1. سنقوم أولا بتوظيب الشال الذي سيتم رفعه . عبارة عن سكريبت php ملغوم ، سأضع فيه هذه الشيفرة البسيطة : <?php phpinfo();?>
    و أحفظه بإسم "mime-shell.php" مثلا .
  2. ثم سأذهب إلى صفحة رفع الملفات ، على المتصفح .
  3. قبل رفع الملف ، سأفتح نافذة "Live HTTP Headers " من شريط أدوات المتصفح "tools"
  4. أعود بعد ذلك إلى صفحة رفع الملفات . ثم سأقوم برفع شال "mime-shell.php" . بطبيعة الحال ، سيتم رفض الملف ، لأن صيغة "text/x-php" غير مسموح بها .
  5. لتغيير صيغة الملف . سأعود مرة أخرى لنافذة HTTP Headers ، ثم سأبحث عن السطر الذي يضم صيغة الملف ، ثم سأقوم باختياره ، كما تبين الصورة أسفله :



    بعد اخيتار هذا السطر ، سأنقر على مفتاح "Replay" ، أسفل النافذة .
  6. ستظهر نافذة جديدة مُجزّءة إلى قسمين ، ستتيح لنا تغيير ما نريده . في الجزء الأسفل ، سنبحث عن الصيغة "text/x-php" ثم نستبدلها بالصيغة التي نريدها من الصيغ السموح بها ، مثلا "image/jpeg"


    .
  7. بعدها سأنقر على "Replay" لإرسال الملف إلى الخادوم مرة أخرى . لكن هذه المرة بصيغة مُعدّلة . سيتم قبول و رفع الملف . ثم تبدأ عملية الإختراق

التحقق من محتوى الملف

طريقة أخرى جد مُستعملة للتحقق من صحة الملف . و هي التأكد من صحة محتواه : هل هو صورة ؟ أو سكريبت ؟ . و ترتكز هذه الطريقة على استعمال الدالة getimagesize() . تعتمد هذه المقاربة على كون ، أن لكل صورة أحجام و أبعاد مُعينة . و أي ملف لا يراعي هذا ، سيتم رفضه . عمليا ، سيتم رفض جميع أنواع الملفات . و قبول الصور "الحقيقية" فقط .

الثغرة 1 و استغلالها بدون استعمال getimagesize


لفهم دور هذه الدّالة . لنتخيل أن المهاجم أنشأ شال php يحتوي على سكريبت شبيه بهذا : <?php phpinfo();?> . سيقوم بتسمية هذا الملف ، مثلا : "shell.php.jpg" . سواء تحققنا من صيغة الملف أم لا . سيتم قبول الملف و رفعه . لأن كل المعلومات التي تخص إسم الملف و صيغته صحيحة . يمكنكم تجربة هذا لديكم . بهذه السهولة ، سيم تنفيذ الشال بمجرد عرض "الصورة" الملغومة على المتصفح . في بعض الأحيان ، حسب توظيب خادومكم ، لن يتم تنفيذ الشال ، بدل ذلك ستحصلون على خطأ يشعركم أن الصورة تحتوي بعض الأخطاء . يمكننا تجاوز هذا بعدة طرق ، مثلا باستعمال "live HTTP Headers extention". كما فعلنا سابقا . هذه المرة ، سنقوم بحذف الإمتداد الزائد "jpg." من إسم الملف "shell.php.jpg" سيبقى إسم الملف كالتالي : "shell.php" . و "استمتعوا بتنفيذ الشال" .

محاولة حماية الثغرة 1 باللجوء إلى getimagesize

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

  • [0] : يحدد عرض الصورة
  • [1] : طول الصورة
  • [2] : نوع الصورة
  • [3] : طول و عرض النص الموجود في وسم الصورة :height="xxx" width="yyy"
  • [4] : صيغة الصورة

لنرى مثالا ، سنتحقق من صحة أبعاد الصّورة . سأقتصر على فرض قيود على الحد الأدنى لطول و عرض الصورة . أي إن كانت أبعاد الصورة أقل من التي حددناها سيتم رفضها ، و في نفس الوقت سيتم أيضا رفض الملفات التي لا أبعاد لها ، كالشالات مثلا

<?php 
$minwidth = 16;  // العرض الأدنى
$minheight = 16; // الطول الأدنى 
$image_sizes = getimagesize($_FILES['milaf']['tmp_name']);
if ($image_sizes[0] < $minwidth OR $image_sizes[1] < $minheight)
{
    echo ' تم رفض الصورة 
          لا يجب أن تقل أبعاد الصّورة عن "16*16"
         ';
} else {
    $destination = "uploads/" . $_FILES["milaf"]["name"];
    move_uploaded_file($_FILES["milaf"]["tmp_name"], $destination);
    echo 'تم رفع الملف بنجاح';
    echo '<a href="'.$destination.'" target="_blank"> مشاهدة الملف </a> ';
}
?>

منطقيا ، تعتبر هذه الطريقة سليمة . لأن الدّالة getimagesize سترفض رفع الملفات التي لا طول و لا عرض لها . و هذا حال جميع سكريبتات البرمجة . إذا قمنا برفع ملفنا السابق "shell.php.jpg" ، سيتم رفضه ، لأنه سكريبت php و ليس صورة صحيحة . هذا جميل ، و سيشعركم بالإرتياح ، أليس كذلك ؟
مع الأسف يمكن أيضا تجاوز هذه الحماية ، لندرس الثغرة التالية :

ثغرة getimagesize و استغلالها

هنا أيضا ، يمكن تجاوز هذه الحماية . إذا كانت getimagesize ترفض رفع السكريبتات . ما المانع لدى المهاجم أن يقوم هذه المرة ، برفع صورة حقيقية ، بأحجام و أبعاد و إسم صحيح ؟ لكن الصورة ستكون عبارة عن "حصان طروادة" . لأن المهاجم قام بإضافة الشال الملغوم داخلها .
هذا جد ممكن . يمكنكم أخذ أي صورة تريدون . قوموا بفتحها ببرنام تحرير النصوص الرقمية "HEX" مثل GIMP أو Hexplorer أو jHead . معظم الصور تسمح بإضافة تعاليق داخلها . هذه التعاليق ، في الحالات العادية ، تضم بصمة صاحب الصورة كإسمه مثلا ، و حقوقه ... إلخ . لكن في حالات القرصنة ، يمكن للمهاجم إضافة السكريبت الذي يريده كتعليق ، و حفظ صورته الجديدة بالإسم و الإمتداد الذي يريده ، ثم رفعها إلى الموقع المصاب . أثناء عرضها ، لن تحصلوا على صورة بل سيتم تنفيذ سكريبت php الموجود داخل الصورة .
لتعوا هذا جيدا ، أعطيكم مثالا .
أخذ صورة jpeg عادية . ثم فتحها ببرنام GIMP .
في شريط الأدوات ، أنقروا على "Image" ثم "Propriétés de limage" ثم "commentaire" .
في نافذة التعاليق ، يمكنكم . إضافة أي سكريبت تريدون . مثلا :

<?php phpinfo(); __halt_compiler(); ?>

__halt_compiler() : لتوقيف تنفيذ السكريبت عند هذا الحد . حتى لا يتم اعتبار بيانات الصورة كسكريبت php و تحصلون على خطأ .
هذا مثال لصورة "هرهور" بريء . حقنتها بسكريبت بسيط :



يمكن فعل نفس الشيء أيضا على برانم HEX الأخرى . هذا مثال لنفس العملية . لكن هذه المرة باستعمال "Hexplorer" :

التحقق من امتداد الملف

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

إستعمال اللائحة السوداء

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

<?php 
$black_list = array(".html",".php",".phtml", ".php3", ".php4",".php5","shtml");
foreach ($black_list as $item) {
  if(preg_match("#$item$#", $_FILES['milaf']['name'])) {
     echo "لا نسمح برفع السكريبتات";exit;
  }
}

$destination = "uploads/" . $_FILES["milaf"]["name"];
if (move_uploaded_file($_FILES["milaf"]["tmp_name"], $destination)) {
    echo 'تم رفع الملف بنجاح';
    echo '<a href="'.$destination.'" target="_blank"> مشاهدة الملف </a> ';
} else {
    echo "تم رفض الملف";
}
?>

يمكننا تجاوز هذه الحماية بثلاث طرق ، مختلفة :

  • بما أنه لا يمكننا حصر جميع امتدادات الملفات الغير مرغوب فيها ، قد يتمكن المُهاجم من رفع ملف "htaccess." و داخله سيضيف شيفرة صغيرة ، كالتالي :

    AddType application/x-httpd-php .jpg

    بهذه الشيفرة ، يُعطي أمرا للخادوم لكي يتعامل مع صور "jpg" كأنها سكريبت php ، سيقوم بقراءتها و تنفيذها على هذا الأساس . بهذا سيتمكن من رفع أي صورة jpg ملغومة على الخادوم .
  • الحالة الثانية ، يمكن للخادوم قبول أكثر من امتداد في الملف . مثلا "shell.php.olala" . سيتم قبول الملف . لأن الإمتداد "olala." غير موجود في اللائحة السوداء . أما بالنسبة للخادوم فسيعتمد على الإمتداد المعروف لديه و هو "php" ,و سيتم تنفيذ السكريبت .
  • الطريقة الثالثة : بدل رفع الملف بامتداد "php." بالحروف الصغيرة . سنغيره إلى الحروف الكبيرة : "PHP." . سيتم قبول الشال و رفعه في حالة نسي المُطوّر إضافة الرمز "i" في التعابير العادية : #$item$#i

استعمال اللائحة البيضاء

أنصحكم بعدم استعمال اللائحة السوداء نهائيا ، بدل ذلك سنستعمل اللائحة البيضاء التي تبدو أكثر أماناً .
للحصول على امتداد الملف بصفة آمنة ، سنفعل ذلك عن طريق php . لدينا عدة طرق للوصول لنفس النتيجة . يمكننا استعمال الدالة strrchr أو explode ... سنستعمل هذه الأخيرة مع دالة أخرى : end .

  • explode() : تقوم بتجزيء النص حسب معايير مُحدّدة ، ثم تحتفظ بالأجزاء في جدول . في مثالنا الذي يهمنا هو امتداد الملف سنُجزّء النص اعتمادا على معيار النقطة "." مثال :
    <?php 
    $filename = 'my-image.php.gif';
    $temp = explode(".", $filename);
    print_r($temp);
    ?>

    النتيجة :
    Array (
    [0] => my-image
    [1] => php
    [2] => gif
    )
  • end() ، تعطينا آخر معطى في الجدول . في مثالنا : "gif"
    أظن أنكم فهمتم المغزى من استعمال الدالتين . فالدالة explode ستقوم بتجزيء النص ، و end ستعطينا الجزء الأخير من النص المُجزّء . و بذلك سنكون قد حصلنا على امتداد الملف . بطريقة آمنة .

لنعُد لإعطاء مثال عن طريقتنا للتحقق إذا كان امتداد الملف ، مسموح له . سنُرخّص لامتدادات الصور فقط :

<?php
$allowed_extensions = array("gif", "jpg","jpeg", "png");
$temp = explode(".", $_FILES["milaf"]["name"]);
$file_extension = end($temp);
 
if (in_array($file_extension, $allowed_extensions)) 
{
    $destination = "uploads/" . basename($_FILES['milaf']['name']);
    move_uploaded_file($_FILES["milaf"]["tmp_name"], $destination);
    echo "تم رفع الملف بنجاح :\n";
} else {
    echo 'امتداد الملف غير مقبول';
}
?>

تبدو الطريقة سليمة و صلبة . لكن مع الأسف ، حتّى هذه الحماية يمكن تجاوزها في الحالات الآتية :

  • النقطة و\أو الفراغات الزّائدة في آخر إسم الملف ، قد تؤدّي في بعض الحالات إلى خرق هذه الحماية
  • إذا كان الخادوم يقبل بأكثر من امتداد في إسم الملف . يمكن تجاوز هذه الحماية . نأخذ أي ملف php ثم نضيف له امتداد الصورة ، مثلا "shell.php.jpg" .
  • يمكن تجاوز هذه الحماية أيضا في إصدارات "مُلقم معلومات الأنترنت" IIS6 و الإصدارات القديمة للويندوز . و ذلك بإضافة مثلا "نقطة فاصلة" بعد الإمتداد الأول "shell.php;.jpg"

خلاصة

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