حماية إسم و محتوى الملف
رأينا في الدّرس السابق مجموعة من طرق الحماية ، و تعرفنا أيضا على طرق تجاوزها . هذا لا يعني بتاتا أننا سنستغني عليها . أو ندعها جانبا . لأنها جزء لا يتجزء من مجموعة أخرى من طرق الحماية و المُكمّلة لبعضها . لهذا أدعوكم للتعرف على أهم الطرق الصحيحة التي يجب اتباعها أثناء إتاحة رفع الملفات
حماية إسم و محتوى الملف
- أول شيء ، لا تتيحوا رفع الملفات إلا للأشخاص الموثوق بهم . أو على الأقل ، يجب على الزائر ، أن يكون مسجلا و عضويته مفعّلة على موقعكم ، و لا يسمح له برفع الملف إلا إذا كان مُتّصلا .
- لا تسمحوا أبدا برفع ملف ، دون التأكد من امتداده بواسطة اللائحة البيضاء . و لا تستعملوا نهائيا اللائحة السوداء
<?php
$error = '';
$allowed_extensions = array("gif", "jpg","jpeg", "png");
$temp = explode(".", $_FILES["milaf"]["name"]);
$file_extension = end($temp);
if (!in_array($file_extension, $allowed_extensions))
{
$error .= 'صيغة الملف غير مسموح بها ';
}
يمكننا إضافة التحقق من صيغة الملف . - لا يجب أن يكون إسم الملف أو امتداده فارغا . لهذا ، استعملوا التعابير العادية . يمكنكم أيضا فرض رمز النقطة "." مرة واحدة فقط . حتى لا نسمح بإسم يضم أكثر من امتداد ، مثلا "shell.php.jpg" . في نفس الوقت سننضف النص حتى لا نسمح سوى للحروف و الأرقام اللاتينية ، يجب أيضا عدم السماح بأكثر من 255 حرفا في إسم الملف :
if(!preg_match('#^[a-zA-Z0-9_-]{1,200}\.(jpe?g|gif|png)$#', $_FILES['milaf']['name'])
{
$error .= 'إسم الملف غير صحيح \n';
} - تحديد الحجم الأقصى للملف الذي سيتم رفعه ، حتى لا يتم إثقال و تعطيل الخادوم "denial service" برفع ملفات كبيرة الحجم من طرف المُهاجم ، في المثال أسفله حددت الحجم ب"20 كيلوبايت أي "20000 بايت" . بما أنه يمكن تعطيل الخادوم أيضا بتحميل ملفات كثيرة صغيرة الحجم ، يجب أيضا تحديد الحد الأدنى
if($_FILES["milaf"]["size"] > 20000 OR $_FILES["milaf"]["size"] < 1000)
{
$error .= 'حجم الملف غير المسموح به';
} - استعمال getimagesize()، للتحقق من صحة أبعاد الصّورة . في المثال أسفله ، سأفرض قيودا على الحد الأدنى لطول و عرض الصورة . إفعلوا نفس الشيء أيضا للحد الأقصى
$minwidth = 16;
$minheight = 16;
$image_sizes = getimagesize($_FILES['milaf']['tmp_name']);
if ($image_sizes[0] < $minwidth OR $image_sizes[1] < $minheight)
{
$error .= 'لا يجب أن تقل أبعاد الصّورة عن "16*16"';
} - إذا صادف أن كان لديكم ملفان بنفس الإسم . لا تقوموا باستبدال الملف القديم بالجديد . و إلاّ أتحتم للمهاجم استبدال ملفكم البريء بملفه الملغوم . أو أضعف الخسائر ، إتلاف بياناتكم الأصلية بغير قصد .
if (file_exists("uploads/" . $_FILES["milaf"]["name"]))
{
$error .= "المرجو تغيير إسم الملف ، يوجد ملف يحمل نفس الإسم ";
} - تجنبوا بصفة قطعية إقحام الملفات المرفوعة باستعمال (include, require, ...) كذلك عدم استعمال الدالة eval . لأن هذه الأخيرة تقوم بتنفيذ أي سكريبت php تصادفه ، بطريقة عشوائية .
- دائما ، غيرّوا إسم الملف ، قبل تخزينه بصفة نهائية . حتّى تُصعّبوا من إمكانية المهاجم ، إيجاد ملفه . غيروا أيضا امتداده إذا أمكنكم ذلك .
لتغيير إسم الملف . يمكننا مثلا استعمال الوقت الحالي "time()" ، لإعطاء إسم حصري للملف الجديد ، حتى تتجنبوا احتمال وجود ملفين بنفس الإسم مثال :<?php
//...
$filename = time().".".$file_extension;
$destination = "uploads/".$filename;
move_uploaded_file($_FILES["milaf"]["tmp_name"], $destination);
//...
?>
هذا مثال فقط . يمكنكم الإعتماد على مجموعة من الدوال الخاصة بهذا الشأن ، كما يمكنكم إستعمال أكثر من دالة واحدة لتسمية الملف بإسم حصري . بعض هذه الدوال : "filename() ,uniqid(), MD5(), sha1(), md5_file() , sha1_file()" ...
عندما تقومون بتغيير إسم الملف باستعمال هذه الطرق . سنحتاج إلى تخزين الإسم الجديد في مكان آخر ، حتى يسهل علينا التعامل معه . هذا هو محور درسنا القادم . سنتعرف على طريقة تخزين معطيات الملف في قاعدة البيانات . لن نُخزن طبعا الملف بأكمله حتى لا نثقل القاعدة . بل سنخزن فقط الإسم الجديد للملف و امتداده مع بعض البيانات الثانوية كحجمه و أبعاده ...
في هذه الحالة ، للتحقق من عدم وجود ملفين يحملان نفس الإسم باستعمال file_exists . لن نعتمد على إسم الملف الذي تم رفعه . بل على الإسم الجديد
حماية مكان تخزين الملف :
- أحسن حماية يمكنكم الحصول عليها ، بالإضافة لما رأيناه . هو وضع مكان تخزين الملفات المرفوعة ، خارج نطاق موقعكم . هذه الإمكانية ، غالبا متاحة للذين يتوفرون على خادومهم الخاص عند المستضيف "Dedicated server" . كما يجب كتابة سكريبت للسمحاح بالتعامل مع هذه الملفات من داخل الموقع .
- تغيير رخص ملف "uploads" بحيث يجب ألا نسمح بتنفيذ السكريبتات . في هذه الحالة ، حتّى إذا تمكن المهاجم من رفع ملفه ، فلن يتمكن من تنفيذه . توجد طرق عديدة لفعل ذلك . سنعتمد على إنشاء صفحة "htaccess." على برنام "++Notepad" مثلا . و نضع فيها الشيفرة التالية . هذه الشيفرة ستمنع من تنفيذ سكريبتات php :
php_flag engine off
ثم سنقوم بحفظ الصفحة في مكان ، بحيث لا يجب أن تتواجد في نفس المكان مع الملفات المرفوعة . و إلا قد يتمكن المهاجم من رفع ملف "htaccess." و يبدل ملفكم الأصلي بملفه . لهذا إذا كنتم تضعون الملفات المرفوعة مباشرة داخل ملف "uploads" . يجب أن تحفظوا صفحة "htaccess." خارج ملف "uploads" . في هذه الحالة ، احذروا ، فجميع الملفات التي ستتواجد في نفس المستوى مع ملف "uploads" ستُأثر عليها صفحة "htaccess."
إذا حصلتم على خطأ "ERROR 500" ، عدّلوا الشيفرة كالآتي :<IfModule mod_php5.c>
php_flag engine off
</IfModule>
إذا كان متاح لكم الولوج إلى ملف httpd.conf على خادومكم ، بدل إنشاء صفحة "htaccess." السابقة ، أضيفوا فقط الشيفرة أسفله ل virtualhost مع تعديل الطريق إلى ملف "uploads"<VirtualHost *:80>
# ...
<Directory /path/to/webroot/to/uploads>
deny from all
<Files ~ "^\w+\.(gif|jpe?g|png)$">
order deny,allow
allow from all
</Files>
<IfModule mod_php5.c>
php_flag engine off
</IfModule>
</Directory>
</VirtualHost>
هذه الطرق كلها تصب في مقاربة واحدة ، هي أنه حتّى إذا تمكّن المُهاجم من رفع الشال فلن يتمكّن من تنفيذه . - يمكننا أيضا حفظ الملفات خارج نطاق الموقع ، بتخزينها في قاعدة البيانات ، باستعمال حقل من نوع "Blob". و لن نحتاج هنا إلى ملف "uploads" . لكن هذه الطريقة ستُثقل كاهل قاعدة البيانات ، نظرا لأحجام الملفات . غالبا لا تُستعمل و ليست مُحبذة ، بخلاف ذلك سنتعرف في الدرس الموالي بتخزين بعض البيانات الخفيفة للملف . ليسهل علينا التعامل معه
- بالإضافة إلى كل هذا ، يجب أن تقوموا بحماية جميع استماراتكم من ثغرات XSS و CSRF . بالنسبة للثغرة الأولى ، تعرفنا عليها سابقا . أما بالنسبة لثغرة CSRF فهذا موضوع ليوم آخر ، بحول الله . أمّا بالنسبة لثغرة "حقن sql" . إذا كنتم تتعاملون مع قاعدة البيانات باستعمال تهييئ الإستعلام "PDO::prepare"، كما رأينا سابقا في هذا الملف . فأنتم محميون من هذه الثغرة .
أنهي هذا الدّرس ، بإعطائكم مثالا لطريقة معالجة الأخطاء .
<?php
$error = '';
if(!preg_match('#^[a-zA-Z0-9_-]{1,200}\.(jpe?g|gif|png)$#', $_FILES['milaf']['name']))
{
$error .= '<p>إسم الملف غير صحيح </p>';
}
// معالجة باقي الأخطاء
// ...
if ($error == '') // إذا لم نسجل أي خطأ ، نسمح برفع الملف
{
// رفع الملف
} else {
// عرض الأخطاء
echo '<h3> تم رفض رفع الملف للأسباب التالية : </h3>';
if($error != '') echo $error;
}
?>أحثكم على تحسين هذا السكريبت بإنشاء دالة . أو مصفوفة أحسن إن كنتم تستخدمون "POO".