مقدمة
منصة للمطورين تحتاج غالباً ميزتين خطيرتين: رفع الملفات (صور، مرفقات) وتشغيل كود يكتبه الأعضاء. كلتاهما تعني إدخال محتوى غير موثوق إلى نظامك. هذا الدرس يلخص كيف نتعامل معهما بأمان، وهو مبني على ما طبقناه فعلاً في عرب كود.
أهداف التعلم
- التحقق من الملفات بمحتواها لا بامتدادها.
- إعادة ترميز الصور وتسليم الملفات بترويسات آمنة.
- جعل الملفات خاصة افتراضياً وتسليمها بفحص صلاحية في كل طلب.
- فهم طبقات عزل تنفيذ الكود غير الموثوق.
الجزء الأول: رفع الملفات
1. لا تثق بالامتداد ولا بـ Content-Type
كلاهما يرسله العميل ويمكنه تزويرهما. افحص التوقيع الفعلي في أول بايتات الملف:
function sniff(buf: Buffer): 'png' | 'jpeg' | 'pdf' | null {
if (buf.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) return 'png';
if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return 'jpeg';
if (buf.subarray(0, 5).toString('latin1') === '%PDF-') return 'pdf';
return null;
}
واقبل قائمة مسموحة من الأنواع فقط، مع حد أقصى للحجم.
2. أعد ترميز الصور
فك الصورة وأعد ترميزها (مثلاً إلى WebP بمكتبة sharp). هذا يزيل البيانات الوصفية (موقع GPS في صور الهاتف!) ويكسر الملفات «المتعددة الأشكال» التي تكون صورة وملفاً آخر في الوقت نفسه.
3. سلّم الملفات بأمان
Content-Type: image/webp (أو application/octet-stream لغير الصور)
Content-Disposition: attachment (لغير الصور: تنزيل لا عرض)
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'none'; sandbox
ولا تقدّم أبداً ملفات HTML أو SVG رفعها المستخدمون من نطاق موقعك الرئيسي كصفحات قابلة للعرض.
4. خاص افتراضياً، وفحص في كل طلب
خطأ شائع: تصنيف الملف «عاماً» لحظة الرفع حسب نوعه. صورة مرفوعة لقسم خاص بالمشرفين ستصبح متاحة لأي من يعرف رابطها. الصحيح:
- الملف خاص افتراضياً، ويُحدد حق قراءته من الشيء المرتبط به (المشاركة وقسمها، المحادثة، المشروع) في كل طلب.
- لا تخزين مؤقت عام طويل لهذه الملفات؛ استخدم
Cache-Control: public, no-cacheمع ETag لما يراه الزوار (يُعاد التحقق في كل مرة)، وprivate, no-storeللخاص. - ربط صورة بمشاركة يتم فقط إن كانت الصورة ملكاً لكاتب المشاركة؛ فلا يستطيع أحد «اقتباس» رابط صورة خاصة في موضوع عام ليكشفها.
- مخزن الكائنات نفسه خاص بالكامل؛ لا روابط عامة دائمة للملفات.
الجزء الثاني: تشغيل الكود غير الموثوق
5. لا تشغّله على خادم التطبيق أبداً
حتى مع timeout وتقييد المستخدم، الكود الذي يعمل على خادم التطبيق يرى ملفاته وشبكته الداخلية وربما أسراره. الحاوية العادية وحدها حدّ عزل ضعيف أمام كود معادٍ لأنها تشارك نواة النظام.
6. طبقات العزل
- حد العزل الأساسي: آلة افتراضية خفيفة (microVM مثل Firecracker) أو نواة معزولة (gVisor)، مؤقتة لكل تنفيذ وتُدمّر بعده.
- بلا شبكة افتراضياً.
- بلا أسرار: لا متغيرات بيئة للتطبيق، ولا وصول لقاعدة الإنتاج.
- مستخدم غير جذري.
- حدود داخلية كدفاع إضافي: مهلة للترجمة وللتشغيل، حد للذاكرة، حد لعدد العمليات (يوقف fork bomb)، حد لحجم الملفات وللمخرجات.
ulimit -u 64 -c 0
ulimit -v 262144 # 256MB لبرامج C/C++ وPython
timeout -k 1 5 ./main < input.txt | head -c 65537 > out.txt
7. لا تدمج الكود في أوامر shell
اكتب كود المستخدم ومدخلاته في ملفات، ولا تضعها أبداً داخل نص أمر. وإن ولّدت سكربت تشغيل من قوالب، فضع الأوامر بين علامات تنصيص مفردة مُهرّبة حتى لا يوسعها الـ shell الخارجي.
8. الحكم على الخادم
في التحديات البرمجية: الخادم يشغّل كل الاختبارات (الظاهرة والمخفية) ويحسب النتيجة بنفسه. لا تقبل أبداً «نجح/فشل» من المتصفح، ولا تكشف مدخلات الاختبارات المخفية في الردود أو رسائل الخطأ، وامنح المكافأة مرة واحدة بقيد فريد داخل معاملة. وعند تعطل خدمة التشغيل، أعد «الخدمة غير متاحة» لا «إجابة خاطئة».
تمرين عملي
- اكتب نقطة رفع تقبل PNG وJPEG فقط بالتوقيع، وتعيد ترميزها إلى WebP، وتختبر أن ملف HTML بامتداد
.pngيُرفض. - اكتب اختباراً يرفع صورة في قسم عام، ثم ينقل الموضوع إلى قسم خاص، ويتأكد أن طلب الرابط نفسه كزائر يعيد 404.
- في حاوية تجريبية بلا شبكة (
docker run --network none ...) شغّل برنامجاً بحلقة لا نهائية وآخر يحاول فتح اتصال شبكي، وتأكد أن المهلة وغياب الشبكة يعملان.
أخطاء شائعة
- الوثوق بالامتداد أو
Content-Type. - تحديد «عام/خاص» مرة واحدة عند الرفع.
- تخزين مؤقت عام لسنة لملفات قد تتغير صلاحياتها.
- تشغيل كود الأعضاء على خادم التطبيق أو في حاوية بشبكة وأسرار.
- تمرير كود المستخدم داخل أمر shell.
الخلاصة
افحص الملفات بمحتواها وأعد ترميز الصور، اجعلها خاصة افتراضياً بفحص صلاحية في كل طلب، وشغّل الكود غير الموثوق في آلة معزولة مؤقتة بلا شبكة ولا أسرار، بحدود زمن وذاكرة وعمليات ومخرجات.
مصادر مقترحة
- OWASP Cheat Sheet: «File Upload».
- توثيق Firecracker وgVisor.
- OWASP: «Unrestricted File Upload».