اختبار التفويض في واجهات API
يفحص اختبار التفويض في واجهات API (API Authorization Testing) أن الواجهة لا تسمح لكل مستدعٍ مُصادَق عليه إلا بما تسمح به سياسة الوصول (Access Policy): الوظائف الصحيحة، على الكائنات الصحيحة، بالحقول الصحيحة. فالمصادقة (Authentication) تجيب عن سؤال: من أرسل الطلب؟ والتفويض (Authorization) يجيب عن سؤال: هل يحق لهذا المستدعي أن يفعل ذلك؟ فالزبون B سجّل دخوله بشكل صحيح، لذا يجتاز الطلب GET /v1/orders/9021 المرسَل برمز (Token) الزبون B فحص المصادقة؛ لكن إذا كانت الطلبية 9021 تخص الزبون A، فيجب أن ترفضه الواجهة مع ذلك.
وأداة المختبِر الرئيسية هنا مصفوفة اختبار (Test Matrix): المستخدمون على جانب، والطلبات في الأعلى، وفي كل خلية الإجابة المتوقعة وفق السياسة.
لماذا يهم
تكشف عيوب التفويض البيانات والعمليات لأشخاص يملكون حساباً صالحاً أصلاً. وتضع قائمة OWASP API Security Top 10 (2023) التفويض المعطوب على مستوى الكائن (Broken Object Level Authorization) في المرتبة الأولى (API1:2023): فغياب فحص ملكية واحد يتيح لأي زبون قراءة عناوين الزبائن الآخرين بتغيير رقم في الرابط.
ولا يُظهر الاستخدام العادي هذه العيوب: فالتطبيق لا يعرض إلا بيانات المستخدم نفسه ويخفي أزرار الإدارة. ولا يكشفها إلا اختبار يستدعي الواجهة عمداً بصفة المستخدم الخطأ.
الأدوار والملكية وسياسة الوصول
يجمع قرار التفويض بين ثلاثة أمور: من المستدعي، وماذا يريد أن يفعل، وأي كائن يمسّ. وتجمع معظم المنتجات بين التحكم في الوصول القائم على الأدوار (RBAC)، حيث يمنح الدور، مثل زبون أو موظف دعم أو مسؤول (Admin)، وظائف محددة، وبين قواعد الملكية: فالزبائن لا يتصرفون إلا في كائناتهم. وتضيف المنتجات متعددة المستأجرين (Multi-tenant) قاعدة أخرى: لا يرى المستخدمون بيانات شركة أخرى أبداً.
وهذه القواعد مكتوبةً هي سياسة الوصول، أي أساس الاختبار (Test Basis) لديك. ويوصي دليل OWASP المختصر للتفويض (Authorization Cheat Sheet) بالرفض الافتراضي، ومنح أقل الصلاحيات، والتحقق من الصلاحيات في كل طلب. وللاختبار يعني ذلك:
- الواجهة هي الحد الفاصل لا الشاشة: إخفاء زر «استرداد المبلغ» لا يحمي شيئاً إذا قبلت
POST /v1/orders/{id}/refundرمز زبون. - يأتي الدور من سجلات الخادم نفسه أو من مطالبة (Claim) في رمز تم التحقق منه، لا من مدخلات العميل مثل ترويسة
X-Role: adminأو"isAdmin": trueفي جسم الطلب. - النطاقات (Scopes) التي رأيناها في درس OAuth تحدّ مما يحق لتطبيق العميل فعله: فالرمز الذي يفتقد النطاق المطلوب يتلقى 403 مع
insufficient_scope. وهي تُضاف إلى فحوص الدور والملكية ولا تحلّ محلها أبداً: فالرمز الذي يحملorders.writeلا يحق له مع ذلك تغيير طلبية زبون آخر. - كما في درس مصادقة واجهات API، يعني 401 غياب بيانات اعتماد صالحة، ويعني 403 رفض مستدعٍ معروف. وتسمح RFC 9110 أيضاً برمز الحالة 404 لإخفاء وجود المورد؛ والعقد يحدد أيهما ينطبق.
ثلاثة مستويات: الوظيفة والكائن والخاصية
يختل التفويض على ثلاثة مستويات، ولكل منها خطر في قائمة OWASP API Security Top 10. والحصول على وظائف دور أعلى هو تصعيد الصلاحيات العمودي (Vertical Privilege Escalation)، والوصول إلى بيانات مستخدم آخر في الدور نفسه هو التصعيد الأفقي (Horizontal).
- مستوى الوظيفة (API5:2023)، أي التفويض المعطوب على مستوى الوظيفة (Broken Function Level Authorization): هل يحق لهذا الدور استدعاء هذه العملية أصلاً؟ زبون يستدعي
GET /v1/admin/users، أو يرسلDELETEمكانGETعلى مسار لا يحق له إلا قراءته. - مستوى الكائن (API1:2023)، أي BOLA، ويُعرف أيضاً باسمه الأقدم IDOR (Insecure Direct Object Reference، أي المرجع المباشر غير الآمن إلى كائن): هل يحق لهذا المستخدم التصرف في هذا الكائن؟ الزبون B يرسل معرّف طلبية A. وتختبئ المعرّفات في المسارات وسلاسل الاستعلام وأجسام الطلبات والترويسات والمسارات المتداخلة مثل
/orders/9021/invoice. - مستوى الخاصية (API3:2023)، أي التفويض المعطوب على مستوى خصائص الكائن (Broken Object Property Level Authorization): هل يحق لهذا المستخدم رؤية هذا الحقل أو ضبطه؟ استجابة تعيد حقولاً لا يحق للمستدعي رؤيتها، أو واجهة تقبل حقلاً لا يحق له ضبطه، وهو ما يُسمّى غالباً الإسناد الجماعي (Mass Assignment).
# Function level: customer A's token on an admin function
GET /v1/admin/users
Authorization: Bearer <token of customer A>
-> expected 403
# Object level: customer B's token on customer A's order
GET /v1/orders/9021
Authorization: Bearer <token of customer B>
-> expected 404 (the contract hides other customers' orders)
# Property level: a customer sends a field only admins may set
PATCH /v1/users/me
Authorization: Bearer <token of customer A>
Content-Type: application/json
{"displayName": "Sara A.", "role": "admin"}
-> expected 400, or the role field ignored; A stays a customerابنِ مصفوفة الاختبار: المستخدمون × الطلبات
تحوّل المصفوفة السياسة إلى حالات اختبار يمكنك عدّها. ابنِها قبل أن ترسل أي طلب:
- الصفوف: مستدعٍ مجهول الهوية، وكل دور، ومستخدمان من الدور نفسه (A وB)، لأن الاختبارات الأفقية تحتاج إلى كائنات لا تخصك؛ ومستخدم من مستأجر آخر أيضاً في المنتجات متعددة المستأجرين.
- الأعمدة: كل نقطة نهاية وكل طريقة في مستند OpenAPI، بما فيها نقاط نهاية الإدارة والبحث والتصدير، والإصدارات الأقدم التي ما زالت منشورة.
- الخلايا: النتيجة المتوقعة وفق السياسة، على كائنات اختبار معروفة المالك، مكتوبة قبل التنفيذ. ومعظم الخلايا حالات رفض، وفيها تختبئ العيوب.
- التنفيذ: لا تغيّر إلا الرمز (Token)، ثم افحص رمز الحالة، والجسم (لا بيانات لمستخدم آخر ولا حقول ممنوعة)، وحالة البيانات: فاستجابة 403 حُفظ معها التغيير رغم ذلك عيب.
- القوائم: يجب ألا تتضمن
GET /v1/ordersللمستخدم B طلبيات A، حتى مع إضافة?customerId=.
جرّبها: مولّد حالات اختبار من OpenAPI جرّبها: مقارنة استجابات API
مثال تطبيقي: واجهة طلبيات
واجهة طلبيات في متجر: الزبائن يقرؤون طلبياتهم ويغيّرونها فقط؛ وموظفو الدعم يقرؤون أي طلبية ويصدرون المبالغ المستردة ولا يغيّرون شيئاً؛ والمسؤولون يستطيعون كل شيء وهم وحدهم من يدير المستخدمين. وطلبية الزبون الآخر تعيد 404. والطلبية 9021 تخص الزبون A. ومن خلايا المصفوفة العشرين لا تسمح بالطلب إلا 8 خلايا.
| المستدعي | R1 · قراءة الطلبيةGET /v1/orders/9021 | R2 · تغيير العنوانPATCH /v1/orders/9021 | R3 · استرداد المبلغPOST /v1/orders/9021/refund | R4 · عرض كل المستخدمينGET /v1/admin/users |
|---|---|---|---|---|
| مجهول الهويةبلا رمز | 401المصادقة | 401المصادقة | 401المصادقة | 401المصادقة |
| الزبون Aمالك الطلبية 9021 | 200مسموح | 200مسموح | 403مستوى الوظيفة | 403مستوى الوظيفة |
| الزبون Bزبون آخر | 404مستوى الكائن | 404مستوى الكائن | 404مستوى الكائن | 403مستوى الوظيفة |
| موظف الدعم | 200مسموح | 403مستوى الوظيفة | 201مسموح | 403مستوى الوظيفة |
| المسؤول | 200مسموح | 200مسموح | 201مسموح | 200مسموح |
- مسموح وفق السياسة، مع رمز النجاح المتوقع
- مرفوض بالتفويض:
403لوظيفة لا يحق للدور استخدامها، و404لطلبية زبون آخر - خلايا مستوى الكائن (BOLA): الدور نفسه، وطلبية شخص آخر
- مرفوض بالمصادقة: لا بيانات اعتماد صالحة،
401
نتائج متوقعة مكتوبة من السياسة قبل التنفيذ. الزبائن يقرؤون طلبياتهم ويغيّرونها فقط؛ وموظفو الدعم يقرؤون أي طلبية ويصدرون المبالغ المستردة ولا يغيّرون شيئاً؛ والمسؤولون يستطيعون كل شيء وهم وحدهم من يدير المستخدمين.
وعند تنفيذها في بيئة الاختبار، برمز مستدعٍ واحد في كل مرة، ظهرت ثلاثة عيوب:
- الزبون B مع R1: رمز الحالة
200مع عنوان A بدلاً من 404. عيب على مستوى الكائن بخطورة «حرج» (Critical): يستطيع أي زبون قراءة أي طلبية. - الزبون A مع R3: رمز الحالة
201، أي مبلغ مسترد لطلبيته هو. عيب على مستوى الوظيفة بخطورة «حرج»: ملكية الكائن لا تمنح كل وظيفة عليه، والمال هنا يخرج من المتجر. - موظف الدعم مع R2: رمز الحالة
403كما هو متوقع، لكن طلبGETجديداً أظهر العنوان الجديد. عيب على مستوى الوظيفة بخطورة «رئيسي» (Major): رمز الحالة يقول إن الطلب رُفض، لكن التغيير حُفظ. ولم يكشفه إلا فحص حالة البيانات.
النسخة النصية
Orders API, test environment. Order 9021 belongs to customer A.
R1 GET /v1/orders/9021 read the order
R2 PATCH /v1/orders/9021 change the delivery address
R3 POST /v1/orders/9021/refund refund the order
R4 GET /v1/admin/users list all user accounts
Actor R1 R2 R3 R4
anonymous (no token) 401 401 401 401
customer A (owner) 200 200 403 403
customer B 404 404 404 403
support agent 200 403 201 403
admin 200 200 201 200أخطاء شائعة
- الاختبار بحساب واحد لكل دور، فيستحيل اختبار مستوى الكائن.
- افتراض أن إخفاء زر يعني أن نقطة النهاية خلفه ترفض الدور الخطأ.
- فحص رمز الحالة وحده، فينجح رفضٌ غيّر البيانات رغم ذلك، أو قائمة فيها سجلات مستخدمين آخرين.
- تبديل المعرّفات في مسار الرابط وحده، لا في معاملات الاستعلام والأجسام والمسارات المتداخلة ونقاط نهاية القوائم والتصدير.
- تخطي اختبارات مستوى الكائن لأن المعرّفات من نوع UUID عشوائية.
- الاختبار على بيانات الإنتاج أو حسابات زبائن حقيقيين أو أي نظام دون إذن مكتوب.
من منظور ضمان الجودة
اطلب سياسة الوصول أثناء جلسات التنقيح (Refinement)، في صورة جدول دور × عملية، قبل بناء نقاط النهاية. فكتابتها في صورة مصفوفة تكشف فجوات مثل «هل يحق لموظف الدعم رؤية تفاصيل الدفع؟» ما دامت الإجابة عنها قليلة الكلفة؛ يقرر مالك المنتج، وتحوّل أنت الإجابات إلى حالات اختبار.
اتفق على حسابات الاختبار التي يملكها فريق ضمان الجودة في كل بيئة: حسابان على الأقل لكل دور، ومستأجران حيث يلزم. وسجّل المستدعي ودوره، ومالك الكائن، والطلب، ورمز الحالة المتوقع والفعلي، وفحص حالة البيانات، مع إخفاء الرموز (Tokens). وعيب BOLA أو مستوى الوظيفة الذي يكشف بيانات شخصية أو مالية عيب أمني، وخطورته عادةً حرج. أتمت المصفوفة في صورة اختبارات API قائمة على البيانات (Data-driven) في خط الأنابيب (Pipeline)، ولا تختبر إلا أنظمة تملكها أو لديك إذن مكتوب باختبارها.
تمرين عملي
راجع سجل اختبار تفويض لواجهة عيادة
سياسة واجهة عيادة: المرضى يقرؤون مواعيدهم ويلغونها فقط، ولا يغيّرون إلا reason؛ والأطباء يقرؤون المواعيد المسندة إليهم ويضيفون الملاحظات السريرية؛ وموظفو الاستقبال يقرؤون كل المواعيد ويعيدون جدولتها لكنهم لا يرون الملاحظات السريرية أبداً؛ والمسؤولون وحدهم ينشئون حسابات الموظفين. والمواعيد التي لا يحق للمستدعي رؤيتها تعيد 404.
الحسابات: المريضان P1 وP2، والطبيب D1، وموظف الاستقبال RC1. والموعد 5531 يخص P1 ومسند إلى D1. راجع هذا السجل من بيئة الاختبار.
Clinic API, test environment. Appointment 5531: patient P1, doctor D1.
# Actor Request Status Observed
1 P1 GET /v1/appointments/5531 200 P1's appointment
2 P2 GET /v1/appointments/5531 200 P1's appointment
3 P2 POST /v1/appointments/5531/cancel 404 appointment unchanged
4 RC1 GET /v1/appointments/5531 200 body has "clinicalNotes"
5 P1 POST /v1/appointments/5531/notes 403 no note added
6 D1 POST /v1/staff {"role": "doctor"} 201 staff account created
7 P1 PATCH /v1/appointments/5531 200 doctor changed to D7
{"reason": "follow-up", "doctorId": "D7"}- اكتب النتيجة المتوقعة لكل صف من السياسة قبل قراءة النتيجة الملاحَظة.
- صنّف كل صف: صحيح أم عيب، وسمِّ مستوى كل عيب: الوظيفة أو الكائن أو الخاصية.
- رتّب العيوب حسب الخطورة، واكتب عنوان تقرير خطأ لأخطرها.
- اذكر تركيبتين من المستدعي والطلب يفتقدهما السجل.
إظهار الإجابة النموذجية
المتوقع: 1 ← 200؛ 2 ← 404؛ 3 ← 404 دون تغيير؛ 4 ← 200 دون clinicalNotes؛ 5 ← 403 لا 404، لأن P1 يحق له رؤية الموعد؛ 6 ← 403 دون حساب؛ 7 ← بحسب ما يقرره العقد، إما 400 دون أي تغيير، وإما 200 مع تغيير reason وحده وتجاهل doctorId؛ وفي الحالتين يبقى الطبيب D1. والصفوف 1 و3 و5 صحيحة.
حرج (Critical): الصف 2 على مستوى الكائن (BOLA، API1:2023)، إذ يقرأ مريض موعد مريض آخر؛ والصف 6 على مستوى الوظيفة (API5:2023)، إذ ينشئ طبيب حسابات موظفين.
رئيسي (Major): الصف 7 على مستوى الخاصية (الإسناد الجماعي، API3:2023)، إذ يغيّر مريض الطبيب؛ والصف 4 على مستوى الخاصية (كشف البيانات الزائدة، API3:2023)، إذ يرى موظف الاستقبال الملاحظات السريرية، ويُعدّ حرجاً حيث تكون هذه الملاحظات أكثر البيانات حساسية.
عنوان تقرير الخطأ للصف 2: «GET /v1/appointments/{id} يعيد موعد مريض آخر برمز الحالة 200 بدلاً من 404 (P2 يقرأ الموعد 5531 الخاص بـ P1)».
غير مغطى، مثلاً: استدعاء مجهول الهوية (401)؛ وGET /v1/appointments بصفة P2، ويجب ألا يظهر فيها الموعد 5531.
أسئلة شائعة
هل يعيد الطلب المرفوض 403 أم 404؟
كلاهما قد يكون صحيحاً. تعرّف RFC 9110 رمز الحالة 403 لطلب فهمه الخادم ويرفض تنفيذه، وتسمح بالرمز 404 حين لا يريد الخادم كشف وجود المورد. وكثير من الواجهات تعيد 404 لكائنات المستخدمين الآخرين حتى لا يمكن تخمين المعرّفات. المهم أن يحدد العقد إجابة واحدة وأن تلتزم بها الواجهة: فإعادة 403 للمعرّفات الموجودة و404 للمفقودة تخبر المهاجم أي المعرّفات موجود.
معرّفاتنا UUID عشوائية، فهل ما زلنا نحتاج إلى اختبارات مستوى الكائن؟
نعم. المعرّفات غير القابلة للتنبؤ أصعب في التخمين، لكنها تتسرّب عبر الروابط المشتركة والسجلات ولقطات الشاشة واستجابات الواجهة الأخرى. وتوصي OWASP بالمعرّفات غير القابلة للتنبؤ إضافةً إلى فحص التفويض لا بديلاً عنه: فلكل معرّف يأتي من العميل يجب أن يتحقق الخادم من أن المستدعي يحق له الوصول إلى ذلك الكائن.
المصفوفة تكبر بسرعة، فهل عليّ اختبار كل خلية؟
رتّب الأولويات حسب المخاطر: ابدأ بكل عملية كتابة، وكل وظيفة إدارية، وكل نقطة نهاية تعيد بيانات شخصية أو مالية، ونقاط نهاية القوائم والبحث والتصدير. ثم أتمت المصفوفة في صورة اختبارات قائمة على البيانات، بصف بيانات لكل مستدعٍ وطلب، فلا تضيف نقطة النهاية الجديدة أو الدور الجديد إلا صفوفاً، وتعمل المصفوفة كاملة مع كل بناء.
هل اختبار التفويض اختبار أمان أم اختبار وظيفي؟
كلاهما. سياسة الوصول متطلب وظيفي، لذا يدخل فحصها في اختبار الواجهات اليومي. ويذهب مختبِرو الاختراق المتخصصون أبعد من ذلك، لكن يجب ألا يكونوا أول من يكتشف أن الزبون B يستطيع قراءة طلبية الزبون A.
المراجع
- OWASP API Security Top 10 (2023)أكثر عشر نقاط ضعف شيوعاً في واجهات API: خلل التفويض على مستوى الكائن، وخلل التوثيق، والاستهلاك غير المقيد للموارد، وغيرها.
- OWASP Cheat Sheet Series: Authorizationضبط الوصول بالشكل الصحيح: أقل الصلاحيات، والرفض افتراضياً، والتحقق من الصلاحيات في كل طلب، وتفضيل النماذج المبنية على السمات أو العلاقات، واختبار منطق التفويض.
- RFC 9110: HTTP Semanticsمعاني أساليب HTTP وحقول الترويسات ورموز الحالة والتفاوض على المحتوى، بغضّ النظر عن إصدار البروتوكول.
- OWASP Web Security Testing Guide (WSTG)المنهجية المرجعية لاختبار تطبيقات الويب وواجهات API بحثاً عن نقاط الضعف الأمنية، اختباراً تلو الآخر.
تدرّب بأدوات متقن
أنشئ طلبات HTTP وأرسلها (الطريقة وعنوان URL والترويسات والمصادقة والجسم) وافحص الاستجابة — بديل خفيف لـ Postman داخل متصفحك.
اختبر نقاط نهاية REST بتحكم كامل في الطريقة والترويسات وجسم JSON، ثم أضف تأكيدات على الحالة والترويسات ومسارات JSON.
قارن استجابتي API (مثل بيئة التجريب مقابل الإنتاج) واحصل على قائمة بكل فرق بنيوي أو في القيم.
ولّد مصفوفة حالات اختبار منظّمة (إيجابية وسلبية ومصادقة وقيم حدّية) من مواصفة OpenAPI / Swagger.
فكّ ترميز ترويسة JWT وحمولته، وفحص المطالبات (claims) والتحقق من انتهاء الصلاحية — بالكامل داخل متصفحك.
اختبر مصادقة Bearer وBasic ومفتاح API والترويسات المخصّصة على نقطة نهاية وقارن الاستجابات المصرّح بها بغير المصرّح بها.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.