السلام عليكم،
سعدت بالمشاركة في المبادرة الصيفية "كود يخدم القرآن" ضمن مشاريع المجتمع مرة أخرى. خلال هذه المشاركة، ساهمت في مشروع RATQ بإنجاز مهمتين أمنيتين لتأمين طبقة البيانات.
أولا: معالجة المشكلة الأمنية في إنشاء مفاتيح الـ API (Issue #152)
رابط الـ Issue:
https://github.com/Itqan-community/RATQ/issues/152
رابط الـ Pull Request:
https://github.com/Itqan-community/RATQ/pull/252
المشكلة:
في هيكلية المشروع، يوجد ملف مسؤول عن إدارة مفاتيح الـ API وتخزينها وهو payload-backend/src/collections/APIKeys.ts. بعد مراجعة الكود وفحص طريقة التحقق في ملفات أخرى مثل AccessRequests.ts، والاطلاع على وصف المشكلة الأمنية وجدت أن النظام كان يعتمد على شرط مصادقة عام وبسيط يسمح لأي مستخدم لمجرد أنه مسجل دخوله بأن يقوم بتوليد مفتاح API Key وربطه مباشرة بأي مورد (Resource) يختاره في النظام، بغض النظر عما إذا كان هو المالك الحقيقي للمورد أم لا، ودون التحقق من وجود أي طلب وصول أو موافقة مسبقة .
طريقة الحل:
لكي أحل هذه المشكلة من جذورها، قمت بالتعديل:
في ملف APIKeys.ts قمت بإضافة حماية تعتمد على beforeValidate Hook قبل الحفظ ليتدخل تلقائيا وفوريا عند محاولة إنشاء أي مفتاح API جديد.
في ملف APIKeys.test.ts أضفت اختبارات وحدة شاملة تغطي كل سيناريوهات النجاح والرفض .
ثم قد قمت بترتيب منطق التحقق داخل الـ Hook بشكل , يمنع أي تداخلات مستقبلا
- باستثناء الأدمن مباشرة للسماح له بالمرور دون قيود.
- التحقق من المالك الحقيقي للمورد المراد ربط المفتاح به عبر استخدام دالة
req.payload.findByID
- في حال لم يكن هو المالك، يتم التحقق مما إذا كان يمتلك تصريح وصول معتمد مسبقا، باستخدام
req.payload.count للبحث في جدول طلبات الوصول والتأكد من وجود طلب حالة اعتماده .
و أخيرا إذا فشلت كل هذه الشروط، يرفض النظام الطلب ويُرجع خطأ برمجيا 403 Forbidden مع رسالة تمنع إتمام العملية.
كما قمت بكتابة اختبارات الوحدة في APIKeys.test.ts لتغطية جميع الحالات.
النتيجة:
إغلاق الثغرة الأمنية تماما وأصبح الـ Backend يرفض أي محاولة لتوليد مفتاح API لمورد لا يملكه المستخدم ولا يملك تصريحا عليه (حتى عند مناداة الـ API مباشرة بلا واجهة).
ثانيا: معالجة الثغرة الأمنية المتعلقة بقراءة الموارد والتعليقات غير المنشورة (Issue #151)
رابط الـ Issue:
https://github.com/Itqan-community/RATQ/issues/151
رابط الـ Pull Request:
https://github.com/Itqan-community/RATQ/pull/254
المشكلة:
في هيكلية المشروع، يوجد ملفان أساسيان وهما payload-backend/src/collections/Resources.ts المسؤول عن إدارة الموارد، و payload-backend/src/collections/Comments.ts المسؤول عن إدارة التعليقات.
بعد فحص الكود والاطلاع على تفاصيل الثغرة، وجدت أن شروط القراءة access.read في كلا الملفين كانت تعتمد على دوال قراءة مفتوحة وغير مقيدة تعيد القيمة (() => true) دائما .
هذا التصميم كان يتيح لأي شخص (سواء كان مستخدما عاديا أو حتى زائرا غير مسجل دخول) أن يقوم بإرسال طلب HTTP مباشر للـ API ، ليتمكن من قراءة مسودات المطورين الأخرى أو التعليقات المرتبطة بها بالكامل دون أي قيود، رغم أن الواجهة الأمامية كانت تخفيها ظاهريا فقط.
طريقة الحل:
لكي أحل هذه المشكلة من جذورها وأحقق تأمين طبقة البيانات، قمت بالتعديل:
- في ملف
Resources.ts قمت بتحديث شروط الوصول للقراءة access.read لتعتمد على منطق متعدد الطبقات يمنح الأدمن صلاحيات كاملة، ويقصر رؤية المسودات حصرا على مالكها الأصلي، بينما يرى الباقون المحتوى المنشور فقط.
- في ملف
Comments.ts قمت بتحديث صلاحيات القراءة فيها باستخدام استعلامات العلاقات النقطية (Dot-Notation مثل 'resource.status' و 'resource.owner') لربط صلاحية قراءة التعليق مباشرة بحالة ومالك المورد الأصلي التابع له.
- في ملفات الاختبارات (
Resources.test.ts & Comments.test.ts) أضفت واختبرت الحالات الإلزامية بدقة مع الالتزام بالأنواع الصارمة في TypeScript لتغطية كل سيناريوهات القراءة والرفض.
النتيجة:
إغلاق الثغرة الأمنية وأصبح الـ Backend يرفض أي محاولة لقراءة أو تسريب أي مسودات أو تعليقات غير منشورة إلا لمن يملكها أوهو الadmin (حتى عند مناداة الـ API مباشرة).
الحمد لله أن وفقنا لهذا و أشكر مجتمع إتقان على هذه المبادرة الطيبة و أخصّ بالشكر الأستاذ @أبو بكر عبد الرحمن صاحب مشروع رتق على توجيهه في هذه المشكلة ومراجعته للكود ونسأل الله أن يبارك في كل الجهود و يكتب الأجر لكل من يساهم و أن يوفقنا لما فيه خير .