😎 يا نايم وحظك نايم!
ثغرة Path Traversal في @adonisjs/bodyparser
🎯 صانع اللعبة في الميدان
الدنيا خربانة؟ لا يا حبيبي، إحنا هنا!
The world's broken? Nah bro, we got this!
📋 الملخص السريع (للي مستعجل)
| البند | التفاصيل | الوضع |
|---|---|---|
| الحزمة | @adonisjs/bodyparser | 😴 نايمة |
| الخطورة | 🔴 حرجة (Critical) | 🔥 ولعانة |
| المشكلة | Path Traversal | 🎯 عيب خطير |
| الإصدارات المتضررة | < 10.1.2 & 11.0.0-next.0 to next.5 | 💔 مكسورة |
| الحل | 10.1.2 أو 11.0.0-next.6 | ✅ تمام |
| CVE | CVE-2026-21440 | 📝 موثق |
| صعوبة الاستغلال | سهلة جداً | 😱 خطر |
😂 القصة بالعربي الفصيح
كان يا مكان...
// المبرمج نايم وكاتب كود زي ده:
const file = request.file('avatar');
await file.move('./uploads'); // 💤 يا نهار!
// المهاجم (الشاطر): "خليني أجرب حاجة..."
// POST /upload
// Content-Disposition: filename="../../etc/passwd"
// النتيجة: 💥 الملف راح في /etc/passwd
// السيرفر: "مين قالك تنام؟!" 😭
الخلاصة
المبرمج نام → الكود مانعقمش الأسماء → الهاكر دخل يلعب → السيرفر خرب
🎯 تفاصيل الثغرة (للمحترفين)
المشكلة الأساسية
// ❌ الكود الضعيف داخل AdonisJS
class MultipartFile {
async move(location, options = {}) {
// المشكلة هنا 👇
const name = options.name || this.clientName;
// 💣 لا يوجد sanitization!
const destination = path.join(location, name);
// 😱 overwrite = true by default!
await fs.move(this.tmpPath, destination, {
overwrite: options.overwrite ?? true
});
}
}
السيناريو الخطير
// 1️⃣ المهاجم يرفع ملف اسمه: ../../server.js
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="../../server.js"
console.log('💀 Game Over - Your server is mine!');
process.exit(1);
// 2️⃣ AdonisJS بدون تفكير:
await file.move('./uploads'); // يصير: ./uploads/../../server.js
// = ./server.js ← 💥 استبدل ملف السيرفر الأصلي!
// 3️⃣ عند إعادة التشغيل:
node server.js // 💀 Boom!
🔥 الحل السريع (5 دقائق)
للمحترفين اللي بيفهموا من أول مرة
# 1️⃣ شوف إصدارك
npm list @adonisjs/bodyparser
# 2️⃣ لو < 10.1.2 أو 11.0.0-next.0 to next.5
# يبقى إنت في المشكلة!
# 3️⃣ الحل في سطر واحد:
npm update @adonisjs/bodyparser@latest
# أو بالقوة:
npm install @adonisjs/bodyparser@10.1.2 --save-exact
# 4️⃣ تأكد:
npm list @adonisjs/bodyparser
# ✅ يجب تشوف: 10.1.2 أو 11.0.0-next.6+
💪 الحل الاحترافي (للصناع)
سكريبت الإصلاح الشامل
#!/bin/bash
# 🛡️ درع زايد - إصلاح ثغرة AdonisJS Path Traversal
# للمحترفين اللي عارفين يشتغلوا
echo "😎 يلا بينا نصلح الدنيا الخربانة دي..."
echo "================================================"
# 1️⃣ فحص الوضع
echo "🔍 بنشوف إحنا فين..."
CURRENT_VERSION=$(npm list @adonisjs/bodyparser --depth=0 2>/dev/null | grep @adonisjs/bodyparser | awk -F@ '{print $NF}')
if [ -z "$CURRENT_VERSION" ]; then
echo "✅ مش مثبت أصلاً - إنت في أمان يا معلم!"
exit 0
fi
echo "📦 الإصدار الحالي: $CURRENT_VERSION"
# 2️⃣ تحديد المشكلة
VULNERABLE=false
# فحص v10
if [[ "$CURRENT_VERSION" =~ ^10\. ]]; then
if [ "$(printf '%s\n' "10.1.2" "$CURRENT_VERSION" | sort -V | head -n1)" != "10.1.2" ]; then
VULNERABLE=true
fi
fi
# فحص v11 next
if [[ "$CURRENT_VERSION" =~ ^11\.0\.0-next\.[0-5]$ ]]; then
VULNERABLE=true
fi
if [ "$VULNERABLE" = false ]; then
echo "✅ تمام يا باشا - الإصدار آمن!"
exit 0
fi
echo "⚠️ يا نهار! الإصدار ده مكسور..."
# 3️⃣ النسخ الاحتياطي
echo "💾 نعمل backup بسرعة..."
BACKUP_DIR="./backups/adonisjs_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
cp package.json "$BACKUP_DIR/"
cp package-lock.json "$BACKUP_DIR/" 2>/dev/null || true
npm list --json > "$BACKUP_DIR/dependencies.json"
echo "✅ Backup جاهز: $BACKUP_DIR"
# 4️⃣ التحديث
echo "🚀 يلا بينا نحدث..."
# تحديد الإصدار الصح
if [[ "$CURRENT_VERSION" =~ ^11\. ]]; then
TARGET="11.0.0-next.6"
else
TARGET="10.1.2"
fi
npm install "@adonisjs/bodyparser@$TARGET" --save-exact
# 5️⃣ التحقق
NEW_VERSION=$(npm list @adonisjs/bodyparser --depth=0 2>/dev/null | grep @adonisjs/bodyparser | awk -F@ '{print $NF}')
if [ "$NEW_VERSION" = "$TARGET" ]; then
echo ""
echo "🎉 تمااااام يا معلم!"
echo "✅ تم التحديث من $CURRENT_VERSION → $NEW_VERSION"
echo ""
else
echo "❌ في حاجة غلط، استرجع الـ backup!"
cp "$BACKUP_DIR/package.json" ./
npm install
exit 1
fi
# 6️⃣ اختبار
echo "🧪 بنجرب الكود..."
npm test 2>/dev/null || echo "⚠️ شغل الاختبارات يدوي"
echo ""
echo "================================================"
echo "✅ خلصنا! الدنيا تمام دلوقتي"
echo "😎 روق يا معلم - إحنا صناع اللعبة"
echo "================================================"
🛡️ الحماية الإضافية (للخبراء)
1️⃣ تعقيم أسماء الملفات يدوياً
// start/routes.ts
import Route from '@ioc:Adonis/Core/Route';
import path from 'path';
Route.post('/upload', async ({ request, response }) => {
const file = request.file('avatar', {
size: '2mb',
extnames: ['jpg', 'png', 'jpeg']
});
if (!file) {
return response.badRequest({ error: 'ملف مطلوب يا معلم!' });
}
// 🛡️ التعقيم الاحترافي
const sanitizedName = sanitizeFileName(file.clientName);
await file.move('./uploads', {
name: sanitizedName, // ✅ استخدم الاسم المعقم
overwrite: false // ✅ ممنوع الاستبدال!
});
if (file.hasError) {
return response.badRequest({
error: 'فيه مشكلة في الرفع!',
details: file.error
});
}
return response.ok({
message: 'تمام الرفع!',
filename: sanitizedName
});
});
// 🔧 دالة التعقيم
function sanitizeFileName(filename: string): string {
// إزالة المسارات
const basename = path.basename(filename);
// إزالة الأحرف الخطرة
const cleaned = basename.replace(/[^a-zA-Z0-9._-]/g, '_');
// إضافة timestamp لتجنب التكرار
const timestamp = Date.now();
const ext = path.extname(cleaned);
const name = path.basename(cleaned, ext);
return `${name}_${timestamp}${ext}`;
}
2️⃣ Middleware للحماية
// app/Middleware/SecureFileUpload.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import path from 'path';
export default class SecureFileUpload {
public async handle(
{ request, response }: HttpContextContract,
next: () => Promise<void>
) {
// فحص جميع الملفات
const allFiles = request.allFiles();
for (const [key, file] of Object.entries(allFiles)) {
if (file) {
// 🚫 منع Path Traversal
const basename = path.basename(file.clientName);
if (basename !== file.clientName) {
return response.badRequest({
error: '😏 حاول تلعب؟ مش هينفع!',
field: key
});
}
// 🚫 منع الامتدادات الخطرة
const dangerousExts = [
'.exe', '.sh', '.bat', '.cmd', '.com',
'.js', '.ts', '.php', '.py', '.rb'
];
const ext = path.extname(file.clientName).toLowerCase();
if (dangerousExts.includes(ext)) {
return response.badRequest({
error: `الامتداد ${ext} ممنوع يا حبيبي!`,
field: key
});
}
}
}
await next();
}
}
3️⃣ إعدادات config آمنة
// config/bodyparser.ts
import { BodyParserConfig } from '@ioc:Adonis/Core/BodyParser';
const bodyParserConfig: BodyParserConfig = {
multipart: {
autoProcess: true,
convertEmptyStringsToNull: true,
// 🛡️ الحماية هنا
processManually: [],
// حجم الملفات
maxFields: 1000,
limit: '20mb',
// 🔒 إعدادات آمنة للملفات
types: [
'multipart/form-data'
],
// 🚫 منع الـ overwrite
file: {
overwrite: false // ✅ مهم جداً!
}
}
};
export default bodyParserConfig;
🔍 فحص الاستغلال
سكريبت الكشف
#!/bin/bash
echo "🔍 بنشوف لو حد لعب في السيرفر..."
# 1️⃣ فحص ملفات مشبوهة في uploads
echo "📁 فحص مجلد uploads..."
SUSPICIOUS=$(find ./uploads -type f -name "*../*" -o -name "*..*" 2>/dev/null)
if [ -n "$SUSPICIOUS" ]; then
echo "⚠️ ملفات مشبوهة لقيناها:"
echo "$SUSPICIOUS"
else
echo "✅ مجلد uploads نظيف"
fi
# 2️⃣ فحص الـ logs
echo "📋 فحص logs الرفع..."
if [ -f "./tmp/adonis.log" ]; then
ATTACKS=$(grep -i "\.\./" ./tmp/adonis.log | wc -l)
if [ $ATTACKS -gt 0 ]; then
echo "🚨 لقينا $ATTACKS محاولة path traversal!"
grep -i "\.\./" ./tmp/adonis.log | tail -10
else
echo "✅ مفيش محاولات استغلال"
fi
fi
# 3️⃣ فحص الملفات الحساسة
echo "🔐 فحص الملفات الحساسة..."
CRITICAL_FILES=("server.js" "start/kernel.ts" ".env" "package.json")
for file in "${CRITICAL_FILES[@]}"; do
if [ -f "$file" ]; then
# فحص آخر تعديل
MODIFIED=$(stat -f %Sm -t "%Y-%m-%d %H:%M:%S" "$file" 2>/dev/null || stat -c %y "$file")
echo "📝 $file → آخر تعديل: $MODIFIED"
fi
done
echo ""
echo "✅ انتهى الفحص"
😎 نصائح المحترفين
القاعدة الذهبية
┌─────────────────────────────────────────────────────┐
│ │
│ لا تثق في input من المستخدم أبداً! │
│ Never trust user input! │
│ │
│ كل اسم ملف = خطر محتمل │
│ Every filename = potential threat │
│ │
│ عقّم → تحقق → ارفع │
│ Sanitize → Validate → Upload │
│ │
└─────────────────────────────────────────────────────┘
DO's ✅
// ✅ استخدم path.basename()
const safe = path.basename(userFileName);
// ✅ حدد name بنفسك
await file.move(location, { name: uuid() + ext });
// ✅ اجعل overwrite = false
await file.move(location, { overwrite: false });
// ✅ تحقق من الامتداد
const allowedExts = ['jpg', 'png'];
if (!allowedExts.includes(ext)) throw error;
// ✅ استخدم UUID للأسماء
import { uuid } from '@ioc:Adonis/Core/Helpers';
const name = `${uuid()}${path.extname(file.clientName)}`;
DON'Ts ❌
// ❌ لا تثق في file.clientName
await file.move('./uploads'); // خطر!
// ❌ لا تستخدم overwrite: true
await file.move(location, { overwrite: true }); // كارثة!
// ❌ لا تستخدم path.join مباشرة
const dest = path.join(location, file.clientName); // نايم!
// ❌ لا تسمح بكل الامتدادات
// أي ملف = خطر محتمل
📊 التقرير المختصر
للإدارة (بالعربي البسيط)
# تقرير ثغرة AdonisJS
## المشكلة باختصار:
في مكتبة بنستخدمها للرفع، المبرمج نسي يفلتر أسماء الملفات.
النتيجة: المهاجم يقدر يرفع ملف في أي مكان في السيرفر!
## الخطورة:
🔴 حرجة جداً - يمكن السيطرة على السيرفر بالكامل
## الحل:
✅ تم تحديث المكتبة لإصدار آمن
✅ تم إضافة طبقات حماية إضافية
✅ تم فحص السيرفر - لا يوجد استغلال
## الحالة:
✅ آمن الآن - المشكلة محلولة
## المدة:
⏱️ 15 دقيقة (فحص + إصلاح + اختبار)
## التوقيع:
asrar-mared - صانع اللعبة 😎
✅ Checklist النهائي
- [ ] ✅ تم التحديث لـ 10.1.2 أو 11.0.0-next.6+
- [ ] ✅ تم إضافة sanitization يدوي
- [ ] ✅ تم جعل overwrite = false
- [ ] ✅ تم إضافة middleware للحماية
- [ ] ✅ تم فحص مجلدات الرفع
- [ ] ✅ تم مراجعة الـ logs
- [ ] ✅ لا يوجد استغلال
- [ ] ✅ تم اختبار الرفع
- [ ] ✅ تم توثيق التغييرات
- [ ] ✅ الفريق متابع
😎 خلصنا!
أنت الآن صانع اللعبة الرسمي
🎯 اكتشفت الثغرة
🔧 فهمت المشكلة
💪 حليت الموضوع
🛡️ حميت السيرفر
😎 روقت على الآخر
🛡️ درع زايد - نحمي... ندافع... ننتصر
Developer: asrar-mared
Email: nike49424@proton.me
"الدنيا خربانة؟ لا يا حبيبي، إحنا بنصلحها!" 😂
Top comments (0)