تعتمد تطبيقات الويب والهواتف الحديثة بشكل شبه كلي على واجهات برمجة التطبيقات REST وGraphQL لتبادل حمولات بيانات JSON المُهيأة. ورغم أن هذا التحول المعماري عزز إنتاجية المطورين بدرجة كبيرة، إلا أنه فتح المجال أمام فئة مميزة من الثغرات الأمنية الناتجة عن أطر العمل الحديثة التي تُقدّم سهولة التطوير على حساب الأمان الصارم. وتظهر ثغرتان أمنيتان بشكل متزامن ومتكامل: التعيين الجماعي (Mass Assignment) المصنفة كـ OWASP API6:2023 والتعرض المفرط للبيانات (Excessive Data Exposure) المصنفة كـ OWASP API3:2023. يتيح تضافر هاتين الثغرتين للمهاجمين استكشاف نماذج البيانات الداخلية وترقية الامتيازات دون كشفهم أو التلاعب في سجلات العمليات. يستعرض هذا المقال كيفية حدوث هاتين الثغرتين، وحوادث واقعية تاريخية، وأمثلة برمجية عبر أشهر التقنيات، وكيفية معالجتها بنجاح.
١. ما هو التعيين الجماعي؟ (OWASP API6:2023)
يحدث التعيين الجماعي (Mass Assignment) عندما تقوم نقطة نهاية API بربط معاملات الطلب المُرسلة من العميل تلقائياً بنماذج البيانات أو خصائص الكائنات في جانب الخادم دون تصفية مسبقة صارمة. في مكتبات التعيين بين الكائنات والبيانات العلاقية (ORM) أو الوثائقية (ODM) الحديثة، يفضل المطورون غالباً بساطة إسناد كائن JSON بالكامل إلى سجل قاعدة البيانات في سطر برمجي واحد.
ومع ذلك، نادراً ما تقتصر نماذج البيانات على الحقول القابلة للتعديل من قبل المستخدم العادي. يحتوي السجل النموذجي للمستخدم في أي تطبيق مؤسسي على سمات مسموح بتعديلها (مثل name أو bio أو phone_number) وحالات داخلية حساسة ومحمية (مثل role أو is_admin أو account_balance أو email_verified أو organization_id). عندما يقوم إطار عمل الـ API بنسخ كل حقل قادم في طلب HTTP عشوائياً إلى الكائن في قاعدة البيانات، يصبح بإمكان أي عميل الكتابة فوق الحقول الداخلية المحمية بمجرد إضافة مفاتيح إضافية في حمولة JSON.
تُصنّف قائمة OWASP لأمان واجهات برمجة التطبيقات ثغرة التعيين الجماعي غير المقيد كـ API6:2023. وهي تمثل خللاً أساسياً في تصميم آليات التخويل: حيث يعتمد الخادم على واجهة المستخدم في تقييد الحقول المُرسلة، بدلاً من فرض تحقق صارم على جانب الخادم.
٢. مثال برمجي مصاب: Node.js / Express + Mongoose
لنتأمل نقطة نهاية لتحديث ملف المستخدم مبنية باستخدام Express وMongoose، حيث يقصد المطور السماح للمستخدم بتعديل اسمه وسيرته الذاتية:
// VULNERABLE: binds all request body fields directly to User model
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const authMiddleware = require('../middleware/auth');
router.put('/api/users/:id', authMiddleware, async (req, res) => {
try {
// التأكد من أن المستخدم يملك الحساب المستهدف
if (req.user.id !== req.params.id) {
return res.status(403).json({ error: 'ممنوع' });
}
// ثغرة أمنية: تمرير كائن الطلب الخام req.body مباشرة إلى قاعدة البيانات
const updatedUser = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
res.json(updatedUser);
} catch (err) {
res.status(500).json({ error: 'خطأ في الخادم' });
}
});
يرسل العميل العادي طلباً مشروعاً كما يلي:
PUT /api/users/64f1a2b3c4e5 HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJhbGci...
{
"name": "أليس سميث",
"bio": "باحثة أمان سيبراني"
}
بينما يقوم المهاجم باختبار نقطة النهاية عبر حقن حقول ذات صلاحيات عالية داخل كائن JSON:
PUT /api/users/64f1a2b3c4e5 HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJhbGci...
{
"name": "أليس سميث",
"role": "admin",
"is_superadmin": true,
"balance": 99999,
"email_verified": true
}
نظراً لأن Mongoose يحدّث أي خاصية معرّفة في المخطط تطابق مفتاحاً في req.body، يُمنح حساب المهاجم صلاحيات المسؤول على الفور بالإضافة إلى رصيد مالي متضخم تعسفياً.
٣. مثال برمجي مصاب: PHP / Laravel Eloquent
توفر Laravel آليات ميسرة للتعيين الجماعي عبر نماذج Eloquent، إلا أن الإعدادات الخاطئة شائعة للغاية:
// VULNERABLE: $fillable غير محمي بشكل صحيح أو استخدام $request->all()
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function update(Request $request, $id)
{
$user = User::findOrFail($id);
// ثغرة: تمرير كافة سمات الطلب الوارد مباشرة
$user->update($request->all());
return response()->json($user);
}
}
يعمد المطورون مراراً إلى تعطيل حماية $fillable في Eloquent عن طريق تعيين protected $guarded = []; لإسكات تحذيرات إطار العمل أثناء التطوير الأولي، أو بوضع جميع الحقول في قائمة $fillable. عندما يُمرر $request->all() إلى update() أو create()، يصبح استغلال الثغرة في غاية السهولة.
٤. الأثر الواقعي وحوادث الاختراق البارزة
التعيين الجماعي ليس ثغرة نظرية مجردة، بل تسبب في بعض أشهر الحوادث الأمنية في تاريخ الويب:
- GitHub (2012): في مارس 2012، استعرض الباحث إيغور هوماكوف ثغرة تعيين جماعي في تطبيق Ruby on Rails الخاص بـ GitHub. كانت GitHub تتيح للمستخدمين رفع مفاتيح SSH العامة. وبسبب الربط التلقائي في Rails، أضاف هوماكوف مفتاحه العام مباشرة إلى المستودع الرئيسي لمشروع Rails على GitHub ونال صلاحية الالتزام المباشر (Direct Commit).
- GitLab (2014): مكنت ثغرة مماثلة في نقاط نهاية دعوة الأعضاء في GitLab المساهمين العاديين من ترقية أدوارهم إلى مالك المشروع عبر إرسال
access_level=50. - برامج مكافآت الثغرات HackerOne: تكشف التقارير العامة باستمرار عن حالات تعيين جماعي في منصات التجارة الإلكترونية والتقنية المالية وبرمجيات SaaS، مما سمح بالتلاعب بحالة التحقق من الهوية (KYC) أو تفعيل اشتراكات مميزة مجاناً ونقل ملكية الفرق المؤسسية.
٥. التعرض المفرط للبيانات (OWASP API3:2023)
يعتمد التعيين الجماعي على معرفة المهاجم بأسماء الخصائص الدقيقة في النموذج الداخلي مثل is_admin أو internal_notes أو stripe_customer_id. كيف يكتشف المهاجم هذه الحقول المخفية؟ الإجابة في معظم الحالات هي التعرض المفرط للبيانات (Excessive Data Exposure).
تحدث هذه الثغرة عندما تقوم واجهة برمجة التطبيقات بتحويل نموذج قاعدة البيانات بالكامل إلى كائن متسلسل وإرساله إلى العميل، معتمدةً على كود الواجهة الأمامية لإخفاء الحقول الحساسة عن الشاشة. نظراً لأن العميل يتلقى حمولة JSON الخام الكاملة عبر الشبكة، يمكن لأي شخص يفحص حركة البيانات عبر أدوات المطورين قراءة تلك السمات:
// استجابة API مصابة: تُعيد سجل قاعدة البيانات كاملاً للمستخدم العادي
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "أليس سميث",
"email": "[email protected]",
"password_hash": "$2b$12$K8y4rUf4zX7zL9qR1vW...",
"is_admin": false,
"role": "standard_user",
"stripe_customer_id": "cus_9x40KzLm8P",
"internal_notes": "خطر احتيال محتمل قيد المراجعة",
"two_factor_secret": "JBSWY3DPEHPK3PXP"
}
يخلق هذا التسريب خطورتين كارثيتين في وقت واحد:
- تسريب مباشر لبيانات بالغة الحساسية: تجزئات كلمات المرور والمفاتيح السرية للمصادقة الثنائية والملاحظات الداخلية.
- تزويد المهاجم بمخطط دقيق لبنية قاعدة البيانات، كاشفاً أسماء الحقول بدقة لاستهدافها في هجمات التعيين الجماعي اللاحقة.
٦. منهجية الاكتشاف: من الاستطلاع إلى الاستغلال
يتبع خبراء الأمن السيبراني ومختبرو الاختراق منهجية واضحة لاكتشاف واستغلال التقاطع بين API3 وAPI6:
- رسم مخطط الاستجابة: إرسال طلب
GET /api/users/meوتحليل كائن JSON المسترجع للتعرف على الحقول التي لا تظهر في واجهة المستخدم. - مقارنة الطلب بالاستجابة: مقارنة حقول طلبات
PUTأوPATCHالصادرة عن العميل بما يظهر في الاستجابة. إذا كان العميل يرسل 3 حقول والاستجابة تعيد 15 حقلاً، فالنظام يعتمد غالباً على الإسناد المباشر للنماذج. - حقن الخصائص: إرسال طلب
PATCHأوPUTيحتوي على أحد الحقول الحساسة المكتشفة في الاستجابة (مثل"role": "admin"). - التحقق من الثبات: إعادة الاستعلام عن المورد عبر
GETلمعرفة ما إذا كانت القيمة المحقونة قد تم تخزينها وثبتت في قاعدة البيانات.
٧. استراتيجيات الدفاع والحلول الجذرية
يتطلب تأمين واجهات برمجة التطبيقات ضد التعيين الجماعي والتعرض المفرط للبيانات اعتماد حدود صارمة على جانب الخادم للتحقق من المدخلات وتصفية المخرجات:
١. استخدام القوائم البيضاء الصريحة (DTOs)
لا تقم مطلقاً بتمرير جسم الطلب الخام مباشرة إلى طبقة قاعدة البيانات. أنشئ كائنات نقل بيانات (DTOs) تحدد الحقول المسموح للعميل بتعديلها حصراً:
// آمن: قائمة بيضاء صريحة وتصفية صارمة لمخرجات الاستجابة
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const authMiddleware = require('../middleware/auth');
router.put('/api/users/:id', authMiddleware, async (req, res) => {
try {
if (req.user.id !== req.params.id) {
return res.status(403).json({ error: 'ممنوع' });
}
// 1. تحديد الحقول المسموح بتعديلها حصراً (القائمة البيضاء)
const allowedFields = {
name: typeof req.body.name === 'string' ? req.body.name.trim() : undefined,
email: typeof req.body.email === 'string' ? req.body.email.trim() : undefined
};
Object.keys(allowedFields).forEach(
key => allowedFields[key] === undefined && delete allowedFields[key]
);
const updatedUser = await User.findByIdAndUpdate(
req.params.id,
allowedFields,
{ new: true, runValidators: true }
);
// 2. إسقاط المخرجات بشكل صريح (عدم تسريب بيانات النموذج الداخلية)
res.json({
id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email
});
} catch (err) {
res.status(500).json({ error: 'خطأ في الخادم' });
}
});
٢. حمايات مخصصة لكل إطار عمل
- Laravel / PHP: تجنب استخدام
$request->all()واستعض عنه بـ$request->only(['name', 'email'])أو Form Requests باستخدام$request->validated(). - Spring Boot / Java: استخدم فئات DTO مستقلة للطلبات ولا تقم بربط JSON الوارد مباشرة مع الكيانات المعلمة بـ
@Entity. - ASP.NET Core: اربط الطلبات بـ ViewModels أو DTOs وتجنب الربط التلقائي على نماذج الكيانات في Entity Framework.
٣. محولات استجابة مخصصة
امنع التعرض المفرط للبيانات عن طريق بناء Resource Classes أو مسلسلات استجابة صارمة تستبعد هيكلياً الحقول الحساسة كالتجزئات والمفاتيح والرموز السرية للمصادقة والملاحظات الداخلية.
٤. أتمتة اختبارات أمان الـ API في CI/CD
ادمج اختبارات العقود لـ OpenAPI وJSON Schema في مسار التطوير المستمر CI/CD. استخدم أدوات مثل Schemathesis وOWASP ZAP للتحقق من رفض نقاط النهاية لأي معلمات غير متوقعة (باستخدام additionalProperties: false في JSON Schema).
الخاتمة
التعيين الجماعي والتعرض المفرط للبيانات وجهان لعملة واحدة: أحدهما يكشف البنية الداخلية للنظام والآخر يمنح المهاجم أداة العبث بها. في عام 2026، حيث تقود الـ APIs كافة التطبيقات الحديثة والخدمات السحابية، فإن الوثوق في مدخلات العميل دون تصفية صريحة يُعد خللاً تصميمياً لا يُغتفر. يتطلب الأمان الحقيقي للـ API فرض حدود صارمة في كل طبقة: قوائم بيضاء للمدخلات وإسقاط محدود للمخرجات.
تقدم كايفورا خدمات متخصصة في تدقيق أمان واجهات برمجة التطبيقات والمراجعات المعمارية واختبارات الاختراق للمؤسسات في المغرب وخارجه. يعمل خبراؤنا على تقييم نقاط النهاية ضد مخاطر OWASP لضمان حماية بياناتكم ومرونة أنظمتكم البرمجية.