DEV Community

CVE-2026-21440

😎 يا نايم وحظك نايم!

ثغرة Path Traversal في @adonisjs/bodyparser

🎯 صانع اللعبة في الميدان

الدنيا خربانة؟ لا يا حبيبي، إحنا هنا!

The world's broken? Nah bro, we got this!

Severity
Status
Mood


📋 الملخص السريع (للي مستعجل)

البند التفاصيل الوضع
الحزمة @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
// السيرفر: "مين قالك تنام؟!" 😭
Enter fullscreen mode Exit fullscreen mode

الخلاصة

المبرمج نام → الكود مانعقمش الأسماء → الهاكر دخل يلعب → السيرفر خرب


🎯 تفاصيل الثغرة (للمحترفين)

المشكلة الأساسية

// ❌ الكود الضعيف داخل 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 
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

السيناريو الخطير

// 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!
Enter fullscreen mode Exit fullscreen mode

🔥 الحل السريع (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+
Enter fullscreen mode Exit fullscreen mode

💪 الحل الاحترافي (للصناع)

سكريبت الإصلاح الشامل

#!/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 "================================================"
Enter fullscreen mode Exit fullscreen mode

🛡️ الحماية الإضافية (للخبراء)

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}`;
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

🔍 فحص الاستغلال

سكريبت الكشف

#!/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 "✅ انتهى الفحص"
Enter fullscreen mode Exit fullscreen mode

😎 نصائح المحترفين

القاعدة الذهبية

┌─────────────────────────────────────────────────────┐
│                                                     │
│  لا تثق في input من المستخدم أبداً!                │
│  Never trust user input!                           │
│                                                     │
│  كل اسم ملف = خطر محتمل                            │
│  Every filename = potential threat                 │
│                                                     │
│  عقّم → تحقق → ارفع                                │
│  Sanitize → Validate → Upload                      │
│                                                     │
└─────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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)}`;
Enter fullscreen mode Exit fullscreen mode

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);  // نايم!

// ❌ لا تسمح بكل الامتدادات
// أي ملف = خطر محتمل
Enter fullscreen mode Exit fullscreen mode

📊 التقرير المختصر

للإدارة (بالعربي البسيط)

# تقرير ثغرة AdonisJS

## المشكلة باختصار:
في مكتبة بنستخدمها للرفع، المبرمج نسي يفلتر أسماء الملفات.
النتيجة: المهاجم يقدر يرفع ملف في أي مكان في السيرفر!

## الخطورة:
🔴 حرجة جداً - يمكن السيطرة على السيرفر بالكامل

## الحل:
✅ تم تحديث المكتبة لإصدار آمن
✅ تم إضافة طبقات حماية إضافية
✅ تم فحص السيرفر - لا يوجد استغلال

## الحالة:
✅ آمن الآن - المشكلة محلولة

## المدة:
⏱️ 15 دقيقة (فحص + إصلاح + اختبار)

## التوقيع:
asrar-mared - صانع اللعبة 😎
Enter fullscreen mode Exit fullscreen mode

✅ Checklist النهائي

  • [ ] ✅ تم التحديث لـ 10.1.2 أو 11.0.0-next.6+
  • [ ] ✅ تم إضافة sanitization يدوي
  • [ ] ✅ تم جعل overwrite = false
  • [ ] ✅ تم إضافة middleware للحماية
  • [ ] ✅ تم فحص مجلدات الرفع
  • [ ] ✅ تم مراجعة الـ logs
  • [ ] ✅ لا يوجد استغلال
  • [ ] ✅ تم اختبار الرفع
  • [ ] ✅ تم توثيق التغييرات
  • [ ] ✅ الفريق متابع

😎 خلصنا!

أنت الآن صانع اللعبة الرسمي

🎯 اكتشفت الثغرة
🔧 فهمت المشكلة  
💪 حليت الموضوع
🛡️ حميت السيرفر
😎 روقت على الآخر
Enter fullscreen mode Exit fullscreen mode

🛡️ درع زايد - نحمي... ندافع... ننتصر

Developer: asrar-mared

Email: nike49424@proton.me

"الدنيا خربانة؟ لا يا حبيبي، إحنا بنصلحها!" 😂

Made with
Status

Top comments (0)