رفع الملف الذي يصير shell
التحقق من نوع mime لا يخبرك ما الملف، وتخزين المرفوعات تحت جذر المستند يعني أن الخادم سينفّذ بكل سرور ما جرى التحقق منه خطأً.
نموذج الرفع مسار يتيح لشخص مجهول وضع ملف على خادمك. وأغلب طرق فشل ذلك تأتي من قرارين اتُّخذا في الساعة الأولى من بنائه.
ماذا يخبرك التحقق فعلًا
$request->validate([
'avatar' => 'required|file|mimes:jpg,png|max:2048',
]);هذا معقول ويبالغ الناس في الثقة به. القاعدة mimes تفحص محتوى الملف لا اسمه،
وهو تحسين حقيقي على فحص الامتداد - لكن "يبدأ بترويسة PNG صالحة" و"هو PNG فقط"
ليستا العبارة نفسها. فقد يحمل ملف ترويسة صورة مشروعة ثم شيفرة PHP، ويظل مستوفيًا
للقاعدة.
وما يجعل ذلك غير ضار ليس تحققًا أفضل. بل أن لا شيء يستطيع تنفيذ الملف.
قاعدتان مجانيتان وتستحقان على أي حال:
'avatar' => 'required|image|dimensions:max_width=4000,max_height=4000|max:2048',القاعدة image أصرم من mimes لرفع الصور، وdimensions ترفض قنبلة فك الضغط -
ملف صغير يتمدد إلى شيء يستنزف الذاكرة حين يفتحه مولّد المصغّرات لديك.
لا تثق بـ getClientOriginalName() ولا getClientMimeType() في أي شيء. كلاهما
يوفّره العميل. الاسم الأصلي صالح للتخزين كتسمية للعرض؛ وغير صالح لبناء مسار
منه.
القرار المهم فعلًا: أين يهبط
// خطأ لمرفوعات المستخدمين
$request->file('avatar')->store('avatars', 'public');القرص public رابط رمزي داخل جذر الويب لديك. وأي شيء هناك يقدّمه خادم الويب
مباشرةً، وإن كان الخادم مضبوطًا لتنفيذ PHP في ذلك المجلد - وكثير منها كذلك،
افتراضًا أو بالخطأ - فالملف الذي اجتاز التحقق صار الآن رابطًا ينفّذ شيفرة.
خزّن المرفوعات على قرص خاص:
$path = $request->file('avatar')->store('avatars'); // خاص افتراضيًاوقدّمها عبر مسار يفحص من يسأل:
public function show(Attachment $attachment)
{
$this->authorize('view', $attachment);
return Storage::download($attachment->path, $attachment->original_name);
}يكلّف ذلك المسار قليلًا من الأداء ويشتري شيئين معًا: لا شيء قابل للتنفيذ، والوصول مخوَّل. وتخزين الكائنات بروابط موقَّعة منتهية الصلاحية هو الترتيب نفسه مع نقل عرض النطاق خارج خادمك.
الأسماء والمسارات والمجلد الأعلى
دع الإطار يولّد الاسم المخزَّن. الدالة store() تنتج اسمًا عشوائيًا أصلًا، ما
يزيل فئة مشكلات كاملة: عبور المسارات من اسم ملف مصنوع، وملفات يطمس بعضها بعضًا،
وأسماء تعني شيئًا غير محمود لصَدَفة النظام.
احتفظ باسم المستخدم الأصلي في عمود قاعدة بيانات للعرض. هما شيئان مختلفان ودمجهما هو مصدر أعطال عبور المسارات.
التقديم هو موضع الخطر المتبقي
SVG ليس صورة. إنه مستند XML قد يحوي برنامجًا نصيًا، وينفّذ المتصفح ذلك البرنامج في الأصل الذي قُدِّم منه. فإن قبلت SVG، فإما أن تنقّيه بمكتبة مبنية لذلك، وإما أن تقدّم ملفات المستخدمين من نطاق منفصل ليعمل ما ينجو من برامج نصية حيث لا يصل إلى ملف ارتباط جلستك.
اضبط نوع المحتوى بنفسك. قدّم نوعًا مخزَّنًا قررته أنت، لا نوعًا مستنبطًا من الملف وقت التنزيل.
افرض تنزيلًا حيث تستطيع. الترويسة Content-Disposition: attachment تعني أن
المتصفح يحفظ بدل أن يرسم، ما يبطل أغلب ما قد يفعله ملف معادٍ داخل صفحة.
انزع البيانات الوصفية من الصور. الصور الفوتوغرافية تحمل EXIF، وEXIF يحمل إحداثيات GPS. ونشر صورة مستخدم مرفوعة بلا معالجة قد ينشر أين التُقطت.
الحدود التي لا يضبطها أحد حتى ينكسر شيء
حدّ الحجم في التحقق ليس حدًّا لما يصل إلى خادمك - يقرر ذلك upload_max_filesize
وpost_max_size في PHP، ولخادم الويب حدّه قبل أن يرى PHP شيئًا. اضبط الثلاثة
عمدًا، وتأكد أن الخطأ الذي يحصل عليه المستخدم عند تجاوزها رسالة لا صفحة بيضاء.
وحدّ معدل مسار الرفع. نقطة نهاية تقبل ملفات بلا حد وسيلة لملء قرصك من الخارج، والقرص الممتلئ يُسقط كل شيء آخر معه.
الفحص، حين تكون قناة توزيع
إن كان المستخدمون يرفعون ملفات ينزّلها مستخدمون آخرون، فالخطر لم يعد خطرك وحدك. افحص بشكل غير متزامن - مهمة في طابور بعد الرفع، والملف معلَّم كغير متاح حتى يجتاز - كي لا يجلس فحص بطيء داخل الطلب.
النسخة القصيرة
تحقق بـ image وdimensions، وخزّن خاصًّا باسم مولَّد، وقدّم عبر مسار مخوَّل
بنوع محتوى اخترته أنت، ولا تدع المرفوعات تهبط أبدًا حيث يكون خادم الويب مستعدًا
لتنفيذها.
أصِب موضع التخزين ويصير التحقق راحةً بدل أن يكون الشيء الوحيد بينك وبين shell.
المسار الذي يقدّم الملف يحتاج النصف الآخر: تحقق من الصلاحية، لا اسمًا يتعذر تخمينه. وكلاهما على القائمة التي يمر عليها التدقيق. ولا يظهر أيٌّ منهما في مراجعة الشيفرة، لأن الشيفرة الموجودة سليمة.
