لنفرض أن فريقك أنهى تطبيقاً للفواتير يعمل بنظام SaaS، وقبل الإطلاق بأسبوع طلب المدير اختبار اختراق Penetration Test، فجاء التقرير بثلاث ملاحظات من النوع الذي يصلح في يوم واحد، وملاحظة رابعة تقول إن أي عميل يستطيع أن يقرأ فواتير عميل آخر بتغيير رقم في الرابط، لأن التطبيق لم يصمم أصلاً على فصل بيانات المستأجرين Tenants. هذه الملاحظة لا تصلح بسطر واحد، فهي تعني تعديل كل استعلام وإضافة عمود إلى الجداول ونقل البيانات القديمة وإعادة الاختبار، والإطلاق بعد أسبوع. والسبب أن السؤال «ما الذي قد يسوء؟» طرح في آخر يوم، على نظام جاهز، بدلاً من أن يطرح في أول يوم على ورقة فيها رسم بسيط للتصميم.
وهذا هو موضوع هذا المقال: نمذجة التهديدات Threat Modeling، أي أن يجلس الفريق مع رسم للنظام ويسأل بطريقة منظمة ما الذي قد يسوء فيه قبل أن يكتب الكود أو قبل أن يغير التصميم، ثم يحول الإجابات إلى قرارات ومهام واختبارات. وسوف نناقش في هذا المقال ما يلي:
- لماذا لا يكفي اختبار الاختراق وفحص الثغرات في آخر المشروع، وما الذي تجده نمذجة التهديدات ولا تجده الأدوات.
- الأسئلة الأربعة لآدم شوستاك Adam Shostack وبيان نمذجة التهديدات Threat Modeling Manifesto.
- الخطوة الأولى: رسم مخطط تدفق البيانات Data Flow Diagram لتطبيق فواتير حقيقي الشكل، مع حدود الثقة Trust Boundaries.
- الخطوة الثانية: البحث عن التهديدات بأسئلة STRIDE الستة، وجدول STRIDE لكل عنصر، ومتى تحتاج LINDDUN أو PASTA أو شجرة الهجوم Attack Tree.
- الخطوة الثالثة: ترتيب المخاطر واتخاذ قرار لكل تهديد، وتحويله إلى تذكرة واختبار.
- الخطوة الرابعة: المراجعة، وكيف يبقى النموذج حياً مع الكود في كل طلب دمج Pull Request.
- الأدوات مفتوحة المصدر وتراخيصها: OWASP Threat Dragon وOWASP pytm وThreagile، وأداة Microsoft المجانية، وألعاب البطاقات.
- تجربة عملية: نصف التطبيق بالكود مع pytm، ونولد المخطط وقائمة التهديدات الحقيقية، ثم نفرز النتائج ونسجل القرارات.
- قالب جاهز للنسخ مع مثال مكتمل من عشرة تهديدات، ثم الأخطاء الشائعة وقائمة تحقق.
الثغرة التي لا يصلحها كود أفضل: لماذا نبحث عن المشاكل في التصميم Design؟
أغلب ما نعرفه من أدوات الأمان يعمل على شيء موجود: أداة تحليل الكود الثابت Static Analysis واختصاراً SAST تقرأ الكود الذي كتبته، وأداة فحص التطبيق أثناء التشغيل Dynamic Analysis واختصاراً DAST تهاجم التطبيق وهو يعمل، واختبار الاختراق يحاول أن يكسر النظام الحقيقي. وكلها مفيدة، ولكنها تجد في الغالب أخطاء في التنفيذ Implementation Bugs، أي ضابطاً Control موجوداً كتب بشكل خاطئ. أما الضابط الذي لم يفكر فيه أحد أصلاً، مثل فصل بيانات المستأجرين أو سجل التدقيق Audit Log أو حد لحجم التصدير، فلا يوجد له كود لتقرأه الأداة، وهنا يأتي دور نمذجة التهديدات.
وهذا التمييز ليس رأياً، فقائمة OWASP Top 10 لعام 2025 تضع التصميم غير الآمن Insecure Design فئة مستقلة في المرتبة السادسة، وتقول صفحة A06:2025 بالنص: «An insecure design cannot be fixed by a perfect implementation as needed security controls were never created to defend against specific attacks»، أي أن التصميم غير الآمن لا يصلحه تنفيذ مثالي، لأن الضوابط المطلوبة لم تصمم أصلاً. ومن توصيات الصفحة نفسها أن تدمج نمذجة التهديدات في جلسات تجهيز المهام Refinement، وأن تستخدم للأجزاء الحساسة مثل المصادقة والتحكم بالوصول ومنطق العمل.
الحلول الأولى التي تخطر على البال، ولماذا لا تكفي
عندما يقرر فريق صغير أن «يهتم بالأمان»، فإنه يبدأ عادة بأحد الحلول التالية، وكل واحد منها له مكانه ولكنه لا يجيب عن سؤال التصميم:
- اختبار اختراق قبل الإطلاق: المختبر يأتي متأخراً ولوقت محدود، فيجد ما يستطيع الوصول إليه في أيام قليلة، وإذا وجد مشكلة في التصميم فقد جاءت في أغلى توقيت ممكن، كما في مثال الفواتير في بداية المقال.
- أداة فحص تعمل في خط CI: أداة SAST أو DAST تجد حقن SQL Injection أو ترويسة Header ناقصة، ولكنها لا تعرف أن لوحة الإدارة يجب ألا تكون على الإنترنت، ولا أن العميل يجب ألا يرى فواتير غيره، لأن هذه قواعد في رأسك وليست في الكود.
- قائمة OWASP Top 10 كقائمة تحقق: القائمة تصف نفسها بأنها وثيقة توعية Awareness Document لأشهر أنواع المخاطر في تطبيقات الويب، وهي مفيدة كتذكير، ولكنها لا تعرف شيئاً عن نظامك: من يتصل بمن، وأين البيانات الحساسة، وأين تتغير الثقة.
- «سوف ننتبه أثناء الكتابة»: كل مطور ينتبه للجزء الذي يكتبه، ولكن أغلب الثغرات الخطيرة تقع بين الأجزاء: بين ال Reverse Proxy والتطبيق، وبين التطبيق وقاعدة البيانات، وبين خدمتك ومزود خارجي، وهذا المكان لا يملكه مطور واحد.
والحل الأنسب هو أن تضيف خطوة رخيصة في البداية: رسم بسيط للنظام، وساعة يسأل فيها الفريق ما الذي قد يسوء، وقائمة قرارات. وهذه الخطوة لا تلغي الأدوات ولا اختبار الاختراق، وإنما تجعلهما يجدان أخطاء التنفيذ فقط، لأن أخطاء التصميم أغلقت قبل أن يكتب الكود.
ما هي نمذجة التهديدات Threat Modeling؟ أربعة أسئلة فقط
يعرف بيان نمذجة التهديدات Threat Modeling Manifesto، الذي كتبته مجموعة من خمسة عشر مختصاً منهم آدم شوستاك، نمذجة التهديدات بأنها «analyzing representations of a system to highlight concerns about security and privacy characteristics»، أي تحليل تمثيل للنظام (رسم أو وصف) لإظهار المخاوف المتعلقة بخصائص الأمان والخصوصية. لاحظ أن التعريف يتحدث عن تمثيل للنظام، وليس عن النظام نفسه، وهذا يعني أنك تستطيع أن تبدأ قبل أن يوجد سطر واحد من الكود.
والطريقة التي يتفق عليها أغلب الممارسين هي ما يسميه شوستاك في صفحته عن نمذجة التهديدات إطار الأسئلة الأربعة Four Question Framework، وهي نفسها الأسئلة الواردة في البيان:
- What are we working on? ماذا نبني؟ والإجابة رسم للنظام.
- What can go wrong? ما الذي قد يسوء؟ والإجابة قائمة تهديدات.
- What are we going to do about it? ماذا سنفعل حيالها؟ والإجابة قرار لكل تهديد.
- Did we do a good enough job? هل قمنا بعمل جيد بما يكفي؟ والإجابة مراجعة واختبارات. (في صفحة شوستاك نفسها تكتب «Did we do a good job?» دون كلمة enough).
والصورة التالية تبين هذه الأسئلة كحلقة تعود إليها مع كل تغيير، وهي الهيكل الذي سوف نمشي عليه في بقية المقال:

ويضيف البيان خمس قيم Values تلخص روح العمل، وأهمها لفريق صغير اثنتان: «People and collaboration over processes, methodologies, and tools»، أي أن الناس والحوار أهم من الطريقة والأداة، و«Continuous refinement over a single delivery»، أي التحسين المستمر بدلاً من وثيقة تسلم مرة واحدة. ويحذر البيان أيضاً من أنماط خاطئة Anti-patterns، منها «Hero Threat Modeler»، أي الاعتماد على شخص خبير واحد يعرف كل شيء، ومنها السعي إلى الرسم المثالي «Perfect Representation». وقد يتساءل البعض: هل هناك طريقة واحدة صحيحة لنمذجة التهديدات؟ والإجابة نجدها في مقال Microsoft القديم Uncover Security Design Flaws Using The STRIDE Approach من عام 2006: «There are multiple approaches to threat modeling, and anyone who tells you his method is the only right one is mistaken»، لذلك سوف نأخذ في هذا المقال الطريقة الأكثر انتشاراً، وهي مخطط تدفق البيانات مع STRIDE، ثم نذكر البدائل ومتى تناسب.
التطبيق الذي سوف نحلله: SaaS صغير للفواتير
سوف نستخدم مثالاً واحداً في كل المقال حتى تكون التهديدات حقيقية وليست عامة. لدينا تطبيق فواتير يخدم عدة شركات (كل شركة مستأجر Tenant)، وفيه ما يلي:
- المتصفح Browser: العميل يسجل الدخول وينشئ الفواتير، والجلسة محفوظة في ملف تعريف ارتباط Cookie.
- ال Reverse Proxy: يستقبل HTTPS من الإنترنت ويمرر الطلبات إلى الخدمات الداخلية عبر HTTP داخل شبكة Docker.
- ال API: خدمة التطبيق الأساسية، تقرأ وتكتب في قاعدة البيانات.
- لوحة الإدارة Admin Panel: تطبيق منفصل لفريق الدعم، يقرأ من قاعدة البيانات نفسها.
- قائمة المهام Job Queue وخدمة البريد Email Worker: ال API يضع مهمة «أرسل الفاتورة» في Redis، والخدمة تأخذها وترسل البريد عبر مزود بريد خارجي بمفتاح API Key.
- قاعدة البيانات PostgreSQL: فيها فواتير كل الشركات وبيانات عملائها.
وهذا التطبيق يشبه ما بنيناه في دليل مدخل إلى تصميم الأنظمة، وهو بسيط بما يكفي لترسمه على ورقة، وفيه بما يكفي من الأجزاء لترى كيف تظهر التهديدات بين الأجزاء وليس داخلها.
الخطوة الأولى: ماذا نبني؟ ارسم مخطط تدفق البيانات DFD
مخطط تدفق البيانات Data Flow Diagram واختصاراً DFD هو رسم بسيط يبين من أين تأتي البيانات وإلى أين تذهب، وهو الطريقة التي تعتمدها Microsoft منذ سنوات لنمذجة التهديدات. وفي المقال المذكور أعلاه تتكون لغة DFD من أربعة عناصر، ويضاف إليها خامس لنمذجة التهديدات:
- الكيان الخارجي External Entity: (ويسمى أيضاً Interactor) طرف خارج سيطرتك يرسل البيانات أو يستقبلها، مثل المستخدم أو متصفحه أو مزود البريد، ويرسم كمستطيل.
- العملية Process: كود يعمل ويعالج البيانات، مثل ال API أو ال Reverse Proxy أو خدمة البريد، ويرسم كدائرة.
- مخزن البيانات Data Store: مكان تبقى فيه البيانات، مثل قاعدة البيانات أو قائمة المهام أو ملفات الرفع أو ملفات السجلات، ويرسم كخطين متوازيين.
- تدفق البيانات Data Flow: بيانات تنتقل بين عنصرين، طلب HTTP أو استعلام SQL أو رسالة في قائمة، ويرسم كسهم.
- حد الثقة Trust Boundary: خط متقطع يفصل بين أماكن تختلف فيها الثقة، مثل الإنترنت وشبكتك الداخلية، أو تطبيقك وخدمة طرف ثالث.
والصورة التالية تبين مخطط تطبيق الفواتير بهذه العناصر، والأرقام على الأسهم هي التي سوف نشير إليها في بقية المقال:

في الصورة أعلاه لاحظ التالي:
- حدود الثقة ليست حدود الشبكة دائماً: رسمنا الشبكة الداخلية وقاعدة البيانات في منطقتين منفصلتين لأن صلاحيات الوصول تختلف، فخدمة البريد في الشبكة نفسها مع ال API ولكنها يجب ألا تصل إلى قاعدة البيانات، وهذا قرار يظهر في الرسم قبل أن يظهر في إعداد الشبكة كما شرحنا في دليل شبكات Docker.
- السهم 3 يحمل ملف تعريف الارتباط بعد أن ينتهي HTTPS: فال Reverse Proxy ينهي التشفير ويمرر الطلب بنص واضح داخل الشبكة، وهذا ليس خطأ بالضرورة، ولكنه قرار يجب أن يكتب.
- مزود البريد خارج حدودك: فكل ما يمر في السهم 7 من أسماء العملاء ومبالغ الفواتير أصبح عند طرف ثالث، ومفتاح API الذي يرسل به هو مفتاح يستطيع أن يرسل بريداً باسم نطاقك.
- لوحة الإدارة عملية مستقلة: لها مسار دخول خاص (السهم 2 ثم 4)، وتصل إلى كل بيانات كل المستأجرين، لذلك هي أعلى عنصر قيمة في الرسم بعد قاعدة البيانات.
كيف تعرف أن الرسم Diagram جيد بما يكفي؟
لا تحاول أن ترسم كل Container وكل منفذ، فالهدف هو أن يفهم كل من في الغرفة أين تمر البيانات، وليس أن تنتج مخطط بنية Architecture Diagram كامل. ويذكر مقال Microsoft قواعد بسيطة لمراجعة الرسم، نلخصها كما يلي:
- لا توجد بيانات تظهر من العدم ولا تختفي في العدم: كل مخزن بيانات يجب أن يكون له من يكتب فيه ومن يقرأ منه، فإذا رسمت قاعدة بيانات تدخلها البيانات ولا يقرؤها أحد فقد نسيت لوحة الإدارة أو التقارير أو النسخ الاحتياطي.
- لا تنتقل البيانات بالتخاطر: البيانات لا تذهب من المستخدم إلى القرص مباشرة، فبينهما دائماً عملية تقرأ وتكتب، وهذه العملية هي التي تحتاج إلى التحقق من المدخلات.
- اجمع العناصر المتشابهة داخل الحد نفسه: عشر نسخ من ال API خلف موزع الحمل Load Balancer عنصر واحد في الرسم ما دامت كلها بالتقنية نفسها وداخل الحد نفسه.
- ابدأ بمستوى عام ثم فصل: رسم السياق Context Diagram يبين النظام كله في صفحة، وإذا احتاج جزء حساس مثل الدفع إلى تفصيل فارسم له رسماً مستقلاً.
ويمكنك أن ترسم المخطط في أي أداة، والأشهر draw.io (ويسمى أيضاً diagrams.net) وهو مفتوح المصدر بترخيص Apache-2.0 ويحفظ الرسم كملف في المستودع، أو في أدوات نمذجة التهديدات التي سوف نراها لاحقاً، أو بالكود كما سوف نفعل في التجربة العملية.
الخطوة الثانية: ما الذي قد يسوء؟ اسأل STRIDE
عندما تنظر إلى الرسم وتسأل «ما الذي قد يسوء؟» بدون أي إطار، فسوف تجد ما تتذكره في تلك اللحظة فقط، والمطور الذي عانى من حقن SQL سوف يجده في كل مكان وينسى الباقي. لذلك نحتاج إلى قائمة أسئلة ثابتة، وأشهرها STRIDE، وهي اختصار لست فئات من التهديدات وضعها Loren Kohnfelder وPraerit Garg في Microsoft في أبريل 1999 كما يروي شوستاك في مقال 20 Years of STRIDE. والفكرة الأساسية أن كل فئة تكسر خاصية أمنية واحدة، كما في جدول مقال Microsoft من عام 2006، والصورة التالية تبين الفئات الست مع الخاصية التي تكسرها ومثال من تطبيق الفواتير:

والآن نمر على الفئات الست واحدة واحدة على تطبيقنا، ولن نشرح الحماية بالتفصيل هنا، لأن لكل واحدة منها دليلاً على الموقع نرسلك إليه.
انتحال الهوية Spoofing: هل هذا فعلاً من يدعي أنه هو؟
الانتحال يكسر خاصية المصادقة Authentication، وهو أن يتصرف شخص أو برنامج باسم غيره. في تطبيقنا أوضح مثال هو ملف تعريف ارتباط الجلسة: إذا سرقه مهاجم عبر ثغرة XSS أو من جهاز مشترك أو من سجل طبع الترويسات كاملة، فإنه يرسل الطلبات إلى ال API والتطبيق يراه العميل نفسه. ومثال آخر أقل وضوحاً: هل تتأكد خدمة البريد أن المهمة في قائمة Redis جاءت فعلاً من ال API، أم يستطيع أي شيء في الشبكة أن يضع مهمة؟ والحماية في خصائص ملفات تعريف الارتباط ودورة حياة الجلسة كما في دليل الجلسات وملفات تعريف الارتباط وJWT، وفي الدخول الموحد كما في OAuth وOpenID Connect، وفي المصادقة الأقوى كما في مفاتيح المرور Passkeys. وانتحال الهوية لا يخص المستخدمين فقط، فرسائل البريد التي ترسلها باسم نطاقك يمكن انتحالها أيضاً، وهذا ما تحميه سجلات SPF وDKIM وDMARC.
العبث بالبيانات Tampering: هل يمكن تغيير البيانات في الطريق أو في المخزن؟
العبث يكسر خاصية السلامة Integrity. ومثاله الكلاسيكي في تطبيقات الويب أن يرسل المتصفح مبلغ الفاتورة أو سعر المنتج في الطلب، فيغيره المستخدم قبل الإرسال، والحل أن يحسب السيرفر المبلغ من البنود ولا يثق بالرقم القادم. ومثال آخر: توكن JWT يحمل role، فإذا قبل ال API توكناً دون أن يتحقق من توقيعه أو قبل الخوارزمية none كما في أخطاء JWT المتكررة، فالمستخدم يكتب صلاحياته بنفسه. وفي الرسم، السهم 3 بنص واضح داخل الشبكة يعني أن أي Container مخترق في الشبكة نفسها يستطيع نظرياً أن يغير الطلبات، والقرار هنا إما تشفير داخلي أو عزل الشبكة وقبول المخاطرة بشكل مكتوب.
الإنكار Repudiation: هل تستطيع أن تثبت من فعل ماذا؟
الإنكار يكسر خاصية عدم الإنكار Non-repudiation، وهو أن يقوم شخص بفعل ثم ينكره ولا يوجد ما يثبت العكس. في تطبيقنا: موظف دعم يحذف فاتورة من لوحة الإدارة أو يغير بريد عميل، ثم يقول «لم أفعل»، فإذا كان سجل التطبيق لا يكتب من فعل ماذا ومتى، أو كان السجل في قاعدة البيانات نفسها التي يستطيع المدير أن يعدلها، فلا يوجد دليل. والحماية سجل تدقيق Audit Log يكتب المستخدم والفعل والمورد والوقت، ويحفظ خارج متناول من يكتب فيه، كما في قسم التدقيق في دليل IAM وPAM، وهذا السجل نفسه هو أول ما سوف تحتاجه عند الحادثة كما في الاستعداد قبل الحادثة في دليل DFIR.
كشف المعلومات Information Disclosure: من يرى ما لا يجب أن يراه؟
كشف المعلومات يكسر خاصية السرية Confidentiality، وهو أخطر فئة في تطبيق SaaS، لأن أشهر صورها أن يرى مستأجر بيانات مستأجر آخر. في تطبيقنا: الرابط /invoices/42 يعيد الفاتورة إذا كان المستخدم مسجلاً دون أن يتأكد أن الفاتورة لشركته، وهذه ثغرة IDOR التي شرحناها في دليل الصلاحيات المركزية، أما فصل بيانات المستأجرين في التصميم نفسه فموضوع دليل تعدد المستأجرين Multi-tenancy. وهناك صور أخرى في الرسم نفسه: ال API الذي يعيد أي Origin في ترويسة CORS كما في دليل CORS، وصفحة الخطأ التي تطبع Stack Trace، والفاتورة التي تذهب كاملة إلى مزود البريد وفيها أكثر مما يحتاجه، وترويسات الاستجابة التي تكشف إصدار السيرفر أو تسمح بعرض الصفحة في موقع آخر كما في دليل ترويسات الأمان HSTS وCSP.
حجب الخدمة Denial of Service: هل يستطيع أحد أن يوقف التطبيق؟
حجب الخدمة يكسر خاصية التوفر Availability. ولا نقصد هنا فقط الهجمات الضخمة من آلاف الأجهزة، وإنما الأشياء الصغيرة التي يكفيها شخص واحد: سكربت يرسل آلاف الطلبات إلى /login، أو طلب تصدير لكل الفواتير منذ عشر سنوات يشغل كل خدمات التصدير، أو رفع ملف ضخم يملأ القرص، أو آلاف المهام في قائمة البريد فلا تصل فواتير العملاء الحقيقيين. والحماية على مستوى ال Reverse Proxy كما في قسم الحد من معدل الطلبات Rate Limiting، وعلى مستوى التطبيق بحدود للحجم ومهلة Timeout وحد لكل مستأجر.
رفع الصلاحيات Elevation of Privilege: هل يستطيع أحد أن يفعل أكثر مما يحق له؟
رفع الصلاحيات يكسر خاصية التفويض Authorization، وهو أن يحصل مستخدم على قدرات لم تعط له. في تطبيقنا: مسارات الإدارة موجودة في ال API نفسه تحت /api/admin، والواجهة لا تعرض الزر للعميل، ولكن الطلب يعمل إذا أرسله يدوياً. أو دور المستخدم يقرأ من حقل في الطلب. والحماية أن يكون التفويض في مكان واحد والرفض هو الافتراضي Deny by Default، كما في دليل الصلاحيات المركزية Authorization.
STRIDE لكل عنصر STRIDE per Element: أي سؤال لأي عنصر؟
إذا سألت الأسئلة الستة لكل عنصر في الرسم فسوف تضيع وقتاً في أسئلة لا معنى لها، فمخزن البيانات لا يسجل الدخول حتى ينتحل هوية أحد. لذلك نشرت Microsoft في مقال 2006 جدولاً يختصر الطريق، يقول أي فئات تنطبق على أي نوع من العناصر، والصورة التالية تبينه مع تطبيقه على مثالنا:

في الصورة أعلاه لاحظ التالي:
- العمليات تأخذ الأسئلة الستة: لأنها الكود الذي يستقبل المدخلات ويتخذ القرارات، لذلك أغلب التهديدات في أي نموذج تقع على ال API وال Reverse Proxy ولوحة الإدارة.
- الأسهم ومخازن البيانات تأخذ T وI وD: أي هل يمكن تغيير البيانات أو قراءتها أو منع وصولها. ويضيف بعض الممارسين سؤال الإنكار R لمخزن يحفظ سجلات التدقيق، لأن من يعدل السجل يمحو الدليل.
- الكيانات الخارجية تأخذ S وR: فأنت لا تتحكم بالمتصفح ولا بمزود البريد، والسؤال الوحيد هو هل تعرف بثقة من هو، وهل تستطيع أن تثبت ما فعله.
- التهديد يظهر غالباً على السهم الذي يعبر حد الثقة: ابدأ بالأسهم 1 و2 و7، ثم العمليات التي تستقبلها، فهي الأكثر تعرضاً.
طرق أخرى: LINDDUN وPASTA وشجرة الهجوم Attack Tree ونظرة المهاجم Attacker
STRIDE ليست الطريقة الوحيدة، وصفحة OWASP Threat Modeling Cheat Sheet توصي باختيار الطريقة حسب الهدف، وفيما يلي أشهرها باختصار:
- LINDDUN للخصوصية Privacy: STRIDE تسأل عن الأمان، أما LINDDUN فتسأل عن الخصوصية، وهي من جامعة KU Leuven في بلجيكا ونشرت أول مرة عام 2010 ومجانية، وحروفها سبع فئات: Linking (ربط بيانات الشخص من مصادر مختلفة) وIdentifying (معرفة هويته) وNon-repudiation (عدم قدرته على إنكار ما لا يريد أن يعرف عنه) وDetecting (معرفة أنه يستخدم الخدمة) وData Disclosure (كشف بياناته) وUnawareness (عدم علمه بما يحدث لبياناته) وNon-compliance (مخالفة الأنظمة). لاحظ أن عدم الإنكار ميزة في STRIDE وتهديد في LINDDUN، لأن ما يحمي النظام قد يضر خصوصية المستخدم. وإذا كان تطبيقك يحفظ بيانات شخصية فاستخدمها مع STRIDE.
- PASTA للمخاطر Risk-centric: اختصار Process for Attack Simulation and Threat Analysis، وضعها Tony UcedaVélez وMarco Morana في كتاب نشرته Wiley عام 2015، وهي سبع مراحل كما في صفحة VerSprite: تحديد الأهداف، ثم النطاق، ثم تفكيك التطبيق، ثم تحليل التهديدات، ثم تحليل الثغرات، ثم نمذجة الهجمات، ثم تقييم المخاطر والأثر. وهي تبدأ من أهداف العمل وتنتهي بالأثر عليه، لذلك تناسب المؤسسات التي لديها فريق مخاطر، وهي أثقل من أن تبدأ بها في فريق من خمسة مطورين.
- شجرة الهجوم Attack Tree: فكرة نشرها Bruce Schneier في مجلة Dr. Dobb's عام 1999 كما في مقاله: هدف المهاجم في أعلى الشجرة، والطرق المختلفة للوصول إليه في الفروع تحته. وهي مفيدة لتهديد واحد مهم تريد أن تفهم كل طرقه، مثل «قراءة فواتير مستأجر آخر»، ففروعه IDOR وحساب دعم مخترق ونسخة احتياطية مكشوفة وتصدير بلا فحص.
- نظرة المهاجم: Kill Chain وMITRE ATT&CK: نموذج Cyber Kill Chain من Lockheed Martin يقسم الهجوم إلى سبع خطوات يجب أن يكملها المهاجم ليصل إلى هدفه، وMITRE ATT&CK قاعدة معرفة لتكتيكات المهاجمين وتقنياتهم مبنية على ملاحظات من هجمات حقيقية. وهما أقرب إلى فرق الدفاع والمراقبة منهما إلى تصميم تطبيق جديد، ولكنهما يساعدانك على السؤال «إذا دخل المهاجم إلى ال Container الأول، فماذا يفعل بعده؟»، وسوف نشرح ATT&CK في مقال مستقل ضمن مرحلة الأمن السيبراني بإذن الله.
- OWASP Top 10 كقائمة تذكير: كما ذكرنا هي وثيقة توعية وليست نموذج تهديدات، فاستخدمها بعد STRIDE لتسأل «هل نسينا فئة مشهورة؟»، ولا تستخدمها بدلاً من رسم نظامك.
الخطوة الثالثة: ماذا سنفعل حيالها؟ التخفيف والإزالة والنقل والقبول Risk Response
بعد الخطوة الثانية سوف يكون لديك قائمة قد تكون طويلة، وأسوأ ما يمكن أن تفعله بها أن تحفظها في ملف وتنساها. فكل تهديد يحتاج إلى قرار واحد من أربعة، كما في صفحة OWASP Threat Modeling Cheat Sheet:
- التخفيف Mitigate: تضيف ضابطاً يقلل احتمال حدوث التهديد أو أثره، مثل الحد من معدل الطلبات على
/login، وهو القرار الأكثر شيوعاً. - الإزالة Eliminate: تحذف الميزة أو العنصر الذي يسبب التهديد، مثل إخراج مسارات الإدارة من ال API العام كلياً، وهذا أقوى قرار لأن الكود غير الموجود لا يمكن مهاجمته.
- النقل Transfer: تحمل طرفاً آخر المسؤولية بعقد أو تأمين، مثل ترك بيانات البطاقات عند مزود الدفع فلا تمر بقاعدة بياناتك أصلاً. لاحظ أن النقل لا يعني أنك أصبحت غير مسؤول، فما زلت مسؤولاً عن طريقة ربطك بالمزود.
- القبول Accept: تقرر أن المخاطرة مقبولة لأن الحماية أغلى من الضرر أو لأن الوقت لا يسمح الآن، وهذا قرار مشروع بشرط أن يكون مكتوباً وله مالك وتاريخ مراجعة، وليس سكوتاً.
ترتيب المخاطر Risk Ranking دون أن تغرق في الأرقام
لن تحل كل التهديدات في هذا الإصدار، لذلك تحتاج إلى ترتيب. وتقول صفحة OWASP إن الترتيب يكون على أساس حاصل ضرب الاحتمال Likelihood في الأثر Impact، مع أنها تعترف بأن تقدير الاثنين صعب. والطريقة الأبسط لفريق صغير أن تضع لكل تهديد احتمالاً منخفضاً أو متوسطاً أو مرتفعاً، وأثراً بالمستويات نفسها، وتبدأ من الزاوية العليا، كما تبين الصورة التالية لتهديدات تطبيق الفواتير العشرة التي سوف نراها في القالب:

وقد يتساءل البعض: ماذا عن DREAD، طريقة الترتيب التي تعطي كل تهديد خمس درجات؟ والإجابة أن آدم شوستاك، الذي قاد نمذجة التهديدات في دورة التطوير الآمن SDL في Microsoft، يضعها في صفحته تحت عنوان «Deprecated» ويذكر أن خبراء نمذجة التهديدات نادراً ما يستخدمونها، لأنها تركز على الأثر والمخاطر وليس على إيجاد التهديدات، ودرجاتها تختلف كثيراً من شخص لآخر. لذلك لا تضيع وقت الاجتماع في نقاش هل الدرجة 6 أم 7، فالشبكة البسيطة تكفي، وإذا كانت مؤسستك تستخدم سجل مخاطر Risk Register رسمياً فانقل إليه التهديدات المقبولة فقط، كما سوف نشرح في مقالات مرحلة الحوكمة والمخاطر والامتثال GRC بإذن الله.
من التهديد إلى تذكرة Ticket واختبار Security Test
القرار الذي لا يتحول إلى عمل في نظام المهام لن ينفذ، لذلك حول كل قرار تخفيف أو إزالة إلى تذكرة Ticket عادية في نظام المهام نفسه الذي يعمل عليه الفريق، واكتب فيها رقم التهديد حتى تعرف لاحقاً لماذا أضيف هذا الكود. والأفضل من ذلك أن تحول التهديد إلى اختبار أمني Security Test Case يفشل إذا عاد التهديد، فالتهديد T3 (المستأجر A يقرأ فاتورة المستأجر B) يصبح اختبار تكامل Integration Test يسجل الدخول كمستخدم من شركة ويطلب فاتورة شركة أخرى ويتوقع الرد 404، والتهديد T2 يصبح اختباراً يتوقع 403 من كل مسار إدارة بتوكن عميل عادي. وهذه الاختبارات من النوع الذي شرحناه في دليل أنواع الاختبارات، وميزتها أنها تعمل في كل تشغيل لخط CI، فلا يعود التهديد بعد سنة عندما يعيد أحد كتابة المسار.
الخطوة الرابعة: هل قمنا بعمل جيد بما يكفي؟ المراجعة Validation
السؤال الرابع هو الذي ينساه أغلب الفرق، وهو في الحقيقة سؤالان. الأول عن هذه الجلسة: هل يطابق الرسم النظام الذي سوف ينشر فعلاً؟ وهل لكل تهديد قرار ومالك؟ وهل تحولت قرارات التخفيف إلى تذاكر واختبارات؟ وهل كان في الغرفة من يعرف كل جزء من الرسم؟ والثاني عن الطريقة نفسها: هل وجدنا شيئاً مفيداً؟ فإذا خرجت ثلاث جلسات متتالية دون أي تهديد جديد فالمشكلة غالباً في الأسئلة أو في الناس وليست في أن النظام آمن.
نموذج التهديدات Threat Model يعيش مع الكود، لا وثيقة تموت في مجلد
أكبر خطأ في نمذجة التهديدات أن تعامل كوثيقة تكتب مرة قبل الإطلاق وتحفظ، فبعد ثلاثة أشهر تضاف ميزة رفع الملفات وتكامل مع مزود دفع وخدمة جديدة، والنموذج القديم لا يعرف عنها شيئاً. والقيمة الخامسة في البيان تقول ذلك صراحة: التحسين المستمر بدلاً من التسليم مرة واحدة. والطريقة العملية لذلك كما يلي:
- عند كل تغيير في التصميم: أي ميزة تضيف سهماً يعبر حد ثقة (مزود خارجي جديد، رفع ملفات، Webhook من طرف آخر، صلاحية جديدة) تحتاج إلى عشر دقائق مع الرسم قبل أن يبدأ الكود.
- في طلب الدمج Pull Request: احفظ الرسم وجدول التهديدات في المستودع بجانب الكود، فإذا غير طلب الدمج البنية فعليه أن يحدث النموذج، والمراجع يرى الاثنين معاً كما في العمل اليومي مع Git.
- النموذج ككود Threat Model as Code: بدلاً من رسم في أداة رسومية، تكتب العناصر والأسهم في ملف Python أو YAML، وتولد منه الرسم وقائمة التهديدات في خط CI، كما سوف نفعل في التجربة مع pytm.
- بعد كل حادثة أو ملاحظة من اختبار اختراق: اسأل لماذا لم يظهر هذا التهديد في النموذج، وأضفه، فالنموذج يتعلم كما يتعلم الفريق.
جلسة Threat Modeling في نصف ساعة لفريق صغير
لا تحتاج إلى ورشة من يومين حتى تبدأ، فلفريق صغير يعمل على ميزة واحدة تكفي جلسة من نحو ثلاثين دقيقة بهذا الترتيب:
- خمس دقائق للرسم: من يعرف الميزة يرسمها على السبورة أو في draw.io: العناصر والأسهم وحدود الثقة، دون تفاصيل.
- خمس عشرة دقيقة لما قد يسوء: المرور على كل سهم يعبر حداً ثم العمليات التي تستقبله، مع جدول STRIDE لكل عنصر، وكتابة كل تهديد في سطر دون نقاش الحلول بعد.
- سبع دقائق للقرارات: لكل تهديد قرار واحد ومالك، وما يحتاج إلى بحث يصبح تذكرة بحث.
- ثلاث دقائق للسؤال الرابع: هل نسينا جزءاً؟ ومتى نعود إلى هذا الرسم؟
ومن يحضر؟ المطور الذي سوف يكتب الميزة، ومطور من الفريق يعرف بقية النظام، ومن يعرف التشغيل والشبكة، ومالك المنتج Product Owner لأنه وحده يعرف ما هو الضرر الحقيقي على العمل، وشخص من فريق الأمن إن وجد، على أن يكون ميسراً Facilitator يسأل وليس صاحب الإجابات. والسبب أن البيان يحذر من «المختص البطل» Hero Threat Modeler، فإذا كان شخص واحد يعرف كل التهديدات فقد أصبح هو نقطة الفشل.
أين تقع نمذجة التهديدات بين SAST وDAST واختبار الاختراق؟
قد يتساءل البعض: إذا كان لدينا SAST وDAST واختبار اختراق سنوي، فهل نحتاج إلى نمذجة التهديدات؟ والإجابة أن كل نشاط ينظر إلى شيء مختلف في وقت مختلف، كما تبين الصورة التالية:

في الصورة أعلاه لاحظ التالي:
- نمذجة التهديدات هي النشاط الوحيد قبل الكود: فهي لا تحتاج إلى تطبيق يعمل، ولذلك هي الأرخص في إصلاح ما تجده.
- SAST وDAST يجدان أخطاء التنفيذ: الأول في الكود والثاني في التطبيق العامل، وسوف نشرحهما مع Semgrep وOWASP ZAP في مقالات مرحلة DevOps بإذن الله، ومنها مقال مصطلحات DevSecOps.
- ما تأخذه من غيرك له نموذج تهديدات خاص: المكتبات وال Docker Images وخط CI/CD نفسه، وهذا موضوع دليل أمن سلسلة التوريد البرمجية Software Supply Chain، ويمكنك أن تضيف خط CI إلى رسمك كعملية لها أسهم إلى المستودع والسيرفر.
- اختبار الاختراق يختبر النموذج: فالمختبر الذي يعرف نموذج التهديدات يوجه وقته إلى الأماكن الأخطر، وكل ما يجده خارج النموذج يعني أن النموذج ناقص.
الأدوات مفتوحة المصدر Open Source والمجانية
الأداة الأهم هي السبورة والقلم، والأدوات التالية تساعدك على الرسم والحفظ والتوليد، وقد راجعنا تراخيصها وحالتها من مستودعاتها وصفحاتها الرسمية في أكتوبر 2026:
OWASP Threat Dragon: رسم ونموذج في المتصفح
OWASP Threat Dragon تطبيق ويب وسطح مكتب لرسم مخطط تدفق البيانات وتسجيل التهديدات لكل عنصر، وهو مشروع OWASP بمستوى Production وترخيصه Apache-2.0، وآخر إصدار عند كتابة المقال هو v2.6.2 في مايو 2026. ويحفظ النموذج كملف JSON على جهازك أو في مستودع GitHub أو GitLab أو Bitbucket، وفي الإصدار الثاني واجهة باللغة العربية. وقد شغلناه في Docker بإعداد التطوير المحلي من صفحة الإعداد على منفذ محلي فقط:
docker run -d --name lab-tm-dragon -p 127.0.0.1:38480:3000 \
-e NODE_ENV=development -e SERVER_API_PROTOCOL=http \
-e ENCRYPTION_KEYS='[{"isPrimary": true, "id": 0, "value": "'$(openssl rand -hex 16)'"}]' \
-e ENCRYPTION_JWT_SIGNING_KEY=$(openssl rand -hex 16) \
-e ENCRYPTION_JWT_REFRESH_SIGNING_KEY=$(openssl rand -hex 16) \
threatdragon/owasp-threat-dragon:stableثم فتحنا http://127.0.0.1:38480 واخترنا Login to Local Session، ثم أحد النماذج التجريبية المرفقة مع الأداة، وهو تطبيق ويب من ثلاث طبقات مأخوذ من OWASP Threat Model Cookbook، وأضفنا إلى الخدمة تهديد انتحال من مثالنا، والصورة التالية تبين النتيجة:

نقاط القوة: واجهة سهلة لمن لا يحب الكود، وتعمل على الجهاز دون حساب، والنموذج ملف يحفظ في المستودع. نقاط الضعف: الرسم يدوي، فإذا تغير النظام ولم يحدث أحد الرسم فلن تعرف، والتهديدات تضاف باليد في أغلب الحالات.
OWASP pytm: النموذج ككود
OWASP pytm مكتبة Python تصف فيها النظام بالكود: العناصر وحدود الثقة والأسهم والبيانات التي تمر فيها وخصائص كل عنصر، ثم تولد مخطط DFD بصيغة Graphviz ومخطط تسلسل Sequence Diagram وقائمة تهديدات من مكتبة قواعد مدمجة أغلب مراجعها من قائمة CAPEC لأنماط الهجمات. ترخيصها MIT، وآخر إصدار 1.4.0 في يوليو 2026 ويحتاج Python 3.11 أو أحدث كما في PyPI. نقاط القوة: النموذج ملف في المستودع يراجع في طلب الدمج ويولد في CI. نقاط الضعف: القواعد عامة وتنتج ضجيجاً كثيراً كما سوف ترى في التجربة، وتحتاج إلى من يفرز النتائج.
Threagile: نموذج YAML وقواعد مخاطر
Threagile أداة مكتوبة بلغة Go تصف فيها البنية والأصول في ملف YAML، فتشغل عليه قواعد مخاطر جاهزة وتولد تقارير ورسوماً، وتعمل من Docker. ترخيصها MIT، والمستودع ما زال يستقبل تعديلات في 2026، ولكن آخر إصدار رسمي هو v0.9.1 من يوليو 2024، والمستودع نفسه يقول إن الواجهة الرسومية ما زالت بعيدة عن الجاهزية، لذلك ثبت الإصدار الذي تستخدمه إذا اعتمدت عليها في CI.
Microsoft Threat Modeling Tool: مجانية ولكنها ليست مفتوحة المصدر
Microsoft Threat Modeling Tool أداة مجانية من Microsoft مبنية على STRIDE per Element وتولد التهديدات تلقائياً من الرسم، ولها قوالب Templates قابلة للتعديل نشرتها Microsoft في مستودع مستقل بترخيص MIT. ولكنها كما في صفحة الإصدارات تعمل على Windows فقط (Windows 10 Anniversary Update أو أحدث مع .NET 4.7.1)، وتحتاج إلى الإنترنت للتحديثات، وآخر إصدار 7.3.51110.1 في نوفمبر 2025، وهي برنامج مغلق المصدر Proprietary. نقاط القوة: توليد تلقائي ناضج وتقارير. نقاط الضعف: Windows فقط، وملفاتها لا تقرأ في أدوات أخرى بسهولة.
ألعاب البطاقات: Elevation of Privilege وOWASP Cornucopia
إذا كان الفريق لا يحب الاجتماعات، فالبطاقات تحول الأسئلة إلى لعبة. Elevation of Privilege لعبة بطاقات صممها آدم شوستاك في Microsoft، كل مجموعة فيها فئة من STRIDE وكل بطاقة تهديد محدد، واللاعب يكسب نقطة إذا ربط بطاقته بعنصر في الرسم، وقد نشرتها Microsoft بترخيص CC BY 3.0 كما في مستودعها. وOWASP Cornucopia لعبة مشابهة لتطبيقات الويب والجوال، مجموعاتها المصادقة والتحقق من المدخلات وإدارة الجلسات والتفويض والتشفير، وترخيصها CC BY-SA 4.0.
مقارنة سريعة بين الأدوات Tools
| الأداة | النوع | الترخيص | التشغيل | يناسب |
|---|---|---|---|---|
| OWASP Threat Dragon | رسم وتسجيل تهديدات | Apache-2.0 | ويب أو سطح مكتب أو Docker | فريق يريد واجهة رسومية مجانية |
| OWASP pytm | نموذج ككود بلغة Python | MIT | مكتبة Python مع Graphviz | فريق يريد النموذج في المستودع وفي CI |
| Threagile | نموذج YAML وقواعد مخاطر | MIT | Docker أو ملف تنفيذي | من يحب YAML ويريد تقارير مخاطر |
| Microsoft Threat Modeling Tool | رسم وتوليد تهديدات | مجانية ومغلقة المصدر | Windows فقط | فرق Windows وAzure |
| draw.io | رسم عام | Apache-2.0 | ويب أو سطح مكتب | رسم DFD سريع يحفظ في المستودع |
| Elevation of Privilege وCornucopia | بطاقات للجلسة | CC BY 3.0 وCC BY-SA 4.0 | مطبوعة أو على الإنترنت | بدء الحوار مع فريق جديد على الموضوع |
وهناك منصات تجارية لنمذجة التهديدات في المؤسسات الكبيرة، مثل IriusRisk، وهي خارج نطاق هذا المقال لأننا نركز على ما تستطيع تشغيله بنفسك.
تجربة عملية: نمذجة تطبيق الفواتير بالكود مع pytm
الآن سوف نصف تطبيق الفواتير نفسه بالكود، ونترك pytm يرسم المخطط ويولد التهديدات، ثم نقرأ النتائج كما يقرؤها فريق حقيقي: ما المفيد وما الضجيج، ثم نسجل القرارات في الملف نفسه. وسوف نشغل كل شيء داخل Container حتى لا نثبت Python وGraphviz على جهازك.
تجهيز ال Container
أنشئ مجلداً للتجربة وشغل Container من Docker Image الرسمية لPython، ثم ثبت Graphviz (لرسم المخطط) وpytm بإصدار محدد:
mkdir -p ~/lab-tm && cd ~/lab-tm
docker run -d --name lab-tm-pytm -v "$PWD:/work" -w /work python:3.12-slim sleep infinity
docker exec lab-tm-pytm sh -c "apt-get update -qq && apt-get install -y -qq graphviz >/dev/null && pip install -q pytm==1.4.0"
docker exec lab-tm-pytm sh -c "pip show pytm | head -3; dot -V"والمخرج سوف يكون كما يلي:
Name: pytm
Version: 1.4.0
Summary: A Pythonic framework for threat modeling
dot - graphviz version 2.42.4 (0)ومن الآن سوف نكتب الأوامر من داخل ال Container، فادخل إليه بالأمر docker exec -it lab-tm-pytm bash.
الخطوة 1: نصف النظام في ملف tm.py
أنشئ الملف tm.py في مجلد التجربة بالمحتوى التالي، وهو المخطط نفسه الذي رسمناه في الخطوة الأولى:
#!/usr/bin/env python3
# Threat model of a small invoicing SaaS (ArabRoot threat modeling explainer)
from pytm import (TM, Actor, Boundary, Dataflow, Datastore, ExternalEntity,
Server, Process, Data, Classification, DatastoreType)
tm = TM("Invoices SaaS")
tm.description = "Small multi-tenant invoicing SaaS: browser, reverse proxy, API, PostgreSQL, admin panel and an email provider"
tm.isOrdered = True
tm.mergeResponses = True
internet = Boundary("Internet")
dmz = Boundary("Edge")
app_net = Boundary("App network")
db_net = Boundary("Database network")
vendor = Boundary("Third party")
customer = Actor("Customer browser")
customer.inBoundary = internet
admin = Actor("Admin")
admin.inBoundary = internet
proxy = Server("Reverse Proxy")
proxy.inBoundary = dmz
proxy.protocol = "HTTPS"
proxy.port = 443
proxy.controls.isHardened = True
proxy.controls.sanitizesInput = False
proxy.controls.encodesOutput = False
api = Server("API")
api.inBoundary = app_net
api.usesSessionTokens = True
api.controls.authenticatesSource = True
api.controls.hasAccessControl = True
api.controls.validatesInput = True
api.controls.sanitizesInput = False
api.controls.encodesOutput = True
api.controls.isHardened = False
api.sourceFiles = ["api/"]
admin_panel = Server("Admin Panel")
admin_panel.inBoundary = app_net
admin_panel.controls.authenticatesSource = True
admin_panel.controls.hasAccessControl = True
admin_panel.controls.isHardened = False
worker = Process("Email Worker")
worker.inBoundary = app_net
db = Datastore("PostgreSQL")
db.inBoundary = db_net
db.type = DatastoreType.SQL
db.isSQL = True
db.controls.isEncryptedAtRest = False
db.controls.hasAccessControl = True
db.storesPII = True
db.storesSensitiveData = True
mail = ExternalEntity("Email Provider")
mail.inBoundary = vendor
creds = Data("Login credentials", classification=Classification.SECRET, isCredentials=True, isPII=False)
session = Data("Session cookie", classification=Classification.SECRET, isCredentials=True)
invoice = Data("Invoice data", classification=Classification.SENSITIVE, isPII=True, isStored=True)
email = Data("Invoice email", classification=Classification.SENSITIVE, isPII=True)
f1 = Dataflow(customer, proxy, "Login and API requests", protocol="HTTPS", dstPort=443, data=[creds, session])
f1.controls.isEncrypted = True
f2 = Dataflow(proxy, api, "Forwarded requests", protocol="HTTP", dstPort=8080, data=[session, invoice])
f2.controls.isEncrypted = False
f3 = Dataflow(api, db, "SQL queries", protocol="PostgreSQL", dstPort=5432, data=invoice)
f3.controls.isEncrypted = False
f4 = Dataflow(db, api, "Query results", protocol="PostgreSQL", data=invoice)
f5 = Dataflow(api, proxy, "API responses", protocol="HTTP", data=invoice)
f6 = Dataflow(proxy, customer, "Responses", protocol="HTTPS", data=invoice)
f6.controls.isEncrypted = True
f7 = Dataflow(admin, proxy, "Admin login", protocol="HTTPS", dstPort=443, data=creds)
f7.controls.isEncrypted = True
f8 = Dataflow(proxy, admin_panel, "Admin requests", protocol="HTTP", dstPort=9000, data=session)
f9 = Dataflow(admin_panel, db, "Admin queries", protocol="PostgreSQL", dstPort=5432, data=invoice)
f10 = Dataflow(api, worker, "Send invoice job", protocol="Redis", dstPort=6379, data=email)
f11 = Dataflow(worker, mail, "Send email (API key)", protocol="HTTPS", dstPort=443, data=email)
f11.controls.isEncrypted = True
if __name__ == "__main__":
tm.process()في الإعداد أعلاه لاحظ التالي:
- العناصر بأنواع DFD:
ActorوExternalEntityللكيانات الخارجية، وServerوProcessللعمليات، وDatastoreلمخزن البيانات، وDataflowللأسهم، وBoundaryلحدود الثقة، وكل عنصر يوضع في حده بالخاصيةinBoundary. - الضوابط Controls هي ما تعرفه عن كل عنصر: مثل
validatesInputوhasAccessControl، والقواعد تقرأ هذه الخصائص، فكل خاصية تتركها على قيمتها الافتراضية تعتبرها الأداة غير موجودة. لذلك أجب عنها بصدق، فالكذبة هنا تخفي تهديداً حقيقياً. - البيانات لها تصنيف Classification: بيانات الدخول والجلسة
SECRETوالفواتيرSENSITIVEوفيها بيانات شخصيةisPII، وبعض القواعد تعمل فقط على الأسهم التي تحمل بيانات حساسة. - رسمنا الردود Responses أيضاً: مثل
Query resultsوAPI responses، والخاصيةmergeResponsesتدمج الطلب ورده في سهم واحد في الرسم.
الخطوة 2: نولد مخطط تدفق البيانات
الأمر --dfd يطبع المخطط بلغة Graphviz، ونمرره إلى dot ليحوله إلى صورة:
python3 tm.py --dfd | dot -Tpng -Gdpi=150 -o dfd.pngوالصورة التالية تبين المخطط الذي ولدته الأداة من الملف دون أي تعديل:

الخطوة 3: نولد قائمة التهديدات Threats
مكتبة القواعد Rules المدمجة في pytm نراها بالأمر --list:
python3 tm.py --list | wc -l
python3 tm.py --list | head -5والمخرج سوف يكون كما يلي:
113
INP01 - Buffer Overflow via Environment Variables
INP02 - Overflow Buffers
INP03 - Server Side Include (SSI) Injection
CR01 - Session Sidejacking
INP04 - HTTP Request Splittingوالتقرير يولد من قالب، والقالب الافتراضي في المستودع يخرج صفحة طويلة، لذلك سوف نكتب قالباً من سطر واحد في الملف table.md يطبع لكل تهديد رقمه وشدته Severity والعنصر ووصفه:
{findings:repeat:{{item:call:getThreatId}} | {{item:call:getFindingSeverity}} | {{item:call:getFindingTarget}} | {{item:call:getFindingDescription}}
}ثم نولد التقرير ونعد التهديدات ونوزعها حسب الشدة:
python3 tm.py --report table.md > findings.txt
grep -c . findings.txt
cut -d"|" -f2 findings.txt | grep . | sort | uniq -c | sort -rnوالمخرج سوف يكون كما يلي:
238
105 Medium
92 High
29 Very High
10 Low
2 Very Low مئتان وثمانية وثلاثون تهديداً لتطبيق من سبع خدمات، وتوزيعها على العناصر كما يلي:
cut -d"|" -f3 findings.txt | grep . | sort | uniq -c | sort -rn 43 Reverse Proxy
40 Admin Panel
36 API
34 Email Worker
8 Login and API requests
8 Forwarded requests
8 Admin requests
8 Admin login
7 Send invoice job
7 Send email (API key)
7 SQL queries
7 Responses
7 Query results
7 Admin queries
7 API responses
4 PostgreSQL لاحظ أن التوزيع يطابق جدول STRIDE لكل عنصر الذي رأيناه: العمليات الأربع عليها أغلب التهديدات، والأسهم عليها سبعة أو ثمانية، ومخزن البيانات عليه أربعة.
الخطوة 4: نفرز النتائج Triage كما يفعل الفريق
هذه القائمة ليست نموذج التهديدات، وإنما مسودة أسئلة. لنأخذ السهم 2، أي الطلبات من ال Reverse Proxy إلى ال API عبر HTTP داخل الشبكة:
grep "| Forwarded requests |" findings.txtDE01 | Medium | Forwarded requests | Interception
AC05 | Medium | Forwarded requests | Content Spoofing
DE03 | Medium | Forwarded requests | Sniffing Attacks
CR06 | High | Forwarded requests | Communication Channel Manipulation
CR08 | Medium | Forwarded requests | Client-Server Protocol Manipulation
DS06 | Very High | Forwarded requests | Data Leak
DR01 | High | Forwarded requests | Unprotected Sensitive Data
AC23 | High | Forwarded requests | Credentials Disclosureوهذه نتائج مفيدة فعلاً: ملف تعريف ارتباط الجلسة وبيانات الفواتير تمر بنص واضح بعد ال Reverse Proxy، وأي شيء في الشبكة نفسها يستطيع أن يقرأها، وهذا هو السؤال الذي كتبناه عند السهم 3 في رسمنا. أما على ال API فسوف تجد نتائج من نوع آخر:
grep -E "PHP|LDAP|SOAP|XML" findings.txt | grep "| API |"INP19 | Medium | API | XML External Entities Blowup
INP22 | High | API | XML Attribute Blowup
INP34 | High | API | SOAP Array Overflowوتطبيقنا لا يستخدم XML ولا SOAP أصلاً. وقد يتساءل البعض: لماذا تظهر هذه التهديدات؟ والإجابة في شرط القاعدة نفسها في ملف القواعد threats.json داخل المكتبة، فشرط INP19 هو target.usesXMLParser is False or target.controls.disablesDTD is False، أي أنه ينطبق حتى عندما تقول إن الخدمة لا تستخدم محلل XML Parser، وشرط INP34 يكفيه أن الخاصية checksInputBounds لم تضبط. وهذا ليس عيباً يمنعك من استخدام الأداة، وإنما يذكرك بأن القواعد العامة تسأل أكثر مما يلزم، والفريق هو الذي يقرر، وأن كل نتيجة تقرأ ثم يكتب لها قرار.
الخطوة 5: نسجل القرارات والافتراضات Assumptions في النموذج نفسه
بعد جلسة المراجعة نضيف القرارات إلى الملف نفسه قبل السطر if __name__ == "__main__":، وكل قرار عليه رقم التذكرة:
# ---- decisions after the review meeting (2026-10) ----
from pytm import Assumption
# Not applicable: the stack has no PHP, LDAP, SOAP or XML parsing
tm.assumptions = [Assumption("No PHP, LDAP, SOAP or XML in this stack",
exclude={"INP16", "INP09", "INP34", "INP19", "INP21", "INP22"})]
# Mitigate (SEC-12): internal services accept TLS only, callers verify certificates
for server in (api, admin_panel, db):
server.controls.isEncrypted = True
for caller in (proxy, api, admin_panel):
caller.controls.authenticatesDestination = True
caller.controls.checksDestinationRevocation = True
# Mitigate (SEC-14): rate limiting and request size limits on the proxy
proxy.controls.handlesResourceConsumption = True
proxy.controls.isResilient = True
# Mitigate (SEC-15): random server-side session ids with expiry
api.controls.usesStrongSessionIdentifiers = Trueفي الإعداد أعلاه لاحظ التالي:
- الافتراض Assumption قرار مكتوب: القواعد الست لا تنطبق على تقنيات لا نستخدمها، وبدلاً من أن نحذفها من التقرير بصمت، كتبنا السبب في النموذج، فإذا أضاف أحد لاحقاً محلل XML فسوف يرى الافتراض ويعرف أنه لم يعد صحيحاً.
- التخفيف يكتب كضابط على العنصر: TLS داخلي بين الخدمات، والحد من الطلبات على ال Proxy، ومعرف جلسة قوي، وكل واحد منها له تذكرة سوف تنفذ وتختبر.
ثم نولد التقرير مرة أخرى:
python3 tm.py --report table.md > findings.txt
grep -c . findings.txt
grep "| Forwarded requests |" findings.txt199
DE03 | Medium | Forwarded requests | Sniffing Attacks
CR06 | High | Forwarded requests | Communication Channel Manipulation
DS06 | Very High | Forwarded requests | Data Leak
AC23 | High | Forwarded requests | Credentials Disclosureانخفض العدد من 238 إلى 199، والتهديدات على السهم 2 من ثمانية إلى أربعة، وما بقي يحتاج إلى قرار آخر، فمثلاً DE03 ينطبق ما دام السهم لا يستخدم VPN، وDS06 ينطبق لأننا لم نحدد أعلى تصنيف بيانات يسمح لكل عنصر أن يستقبله بالخاصية maxClassification. والملف الذي يحفظ النتائج للأدوات الأخرى نولده بصيغة JSON:
python3 tm.py --json tm.json
python3 -c "import json; j=json.load(open('tm.json')); print(len(j['findings']), len(j['excluded_findings']))"199 16لاحظ الرقم الثاني: ستة عشر تهديداً استبعدت بالافتراض، ولكنها لم تختف، فهي محفوظة في excluded_findings مع اسم الافتراض الذي استبعدها، وهذا هو الفرق بين قرار مكتوب وتهديد محذوف.
ماذا يحدث إذا وضعت التشفير على السهم نفسه؟
الطريقة التي تبدو طبيعية لتسجيل TLS الداخلي هي أن تضبطه على السهم، أي f2.controls.isEncrypted = True، وإذا فعلت ذلك بدلاً من الحلقتين أعلاه فسوف تجد أن السهم 2 ما زال عليه التهديدات الثمانية نفسها، وأن العدد الكلي 217. والسبب في الكود نفسه: قبل تطبيق القواعد تملأ pytm القيم الافتراضية للأسهم، فتأخذ isEncrypted من العنصر الذي يستقبل السهم، وتأخذ authenticatesDestination من العنصر الذي يرسله، وتكتب فوق ما وضعته على السهم. لذلك صف التشفير كخاصية في الخدمة التي تقبل TLS فقط، وتحقق دائماً من أن قرارك غير التقرير فعلاً، فالنموذج ككود يحتاج إلى مراجعة مثل أي كود.
إزالة التجربة
احذف ال Containers التي أنشأناها بأسمائها:
docker rm -f lab-tm-pytm lab-tm-dragondocker system prune أو docker volume prune بعد التجربة، لأنها تحذف كل ما ليس قيد التشغيل على الجهاز، ومنها Containers وVolumes لمشاريع أخرى. احذف ما أنشأته بالاسم فقط.قالب Template جاهز للنسخ، ومثال من عشرة تهديدات
إذا لم تكن تريد أداة الآن، فهذا القالب يكفي، احفظه كملف THREATS.md بجانب الكود وحدثه في كل طلب دمج يغير البنية:
# Threat model: <system or feature>
- Scope: <what is in and out>
- Diagram: docs/dfd.drawio (updated: YYYY-MM-DD)
- People: <names and roles who attended>
- Next review: <date or trigger, e.g. "new external provider">
| ID | Element (DFD) | Threat (STRIDE) | Impact | Likelihood | Decision | Mitigation / reason | Owner | Ticket | Status |
|----|---------------|-----------------|--------|------------|----------|---------------------|-------|--------|--------|
| T1 | | | | | | | | | |والجدول التالي مثال مكتمل لتطبيق الفواتير، والأرقام نفسها التي وضعناها على شبكة المخاطر:
| ID | العنصر | التهديد (STRIDE) | الأثر | القرار والتخفيف | المالك | الحالة |
|---|---|---|---|---|---|---|
| T1 | السهم 1 (المتصفح إلى ال Proxy) | S: ملف تعريف ارتباط الجلسة يسرق ويعاد إرساله | مرتفع | تخفيف: HttpOnly وSecure وSameSite، ومهلة خمول قصيرة، وتغيير المعرف عند الدخول | قائد ال Backend | مفتوح (SEC-15) |
| T2 | API | E: عميل يستدعي مسارات /api/admin مباشرة | مرتفع | إزالة: نقل مسارات الإدارة إلى لوحة الإدارة فقط، مع اختبار يتوقع 404 من ال API العام | قائد ال Backend | قيد التنفيذ |
| T3 | API إلى PostgreSQL | I: المستأجر A يقرأ فاتورة المستأجر B بتغيير المعرف | مرتفع جداً | تخفيف: معرف المستأجر في كل استعلام مع Row-Level Security، واختبار تكامل لكل مسار | قائد ال Backend | مفتوح |
| T4 | API | T: المتصفح يرسل مبلغ الفاتورة | متوسط | تخفيف: السيرفر يحسب المبلغ من البنود ويتجاهل الرقم القادم | مطور الفواتير | مغلق |
| T5 | Reverse Proxy | D: تخمين كلمات المرور وإغراق /login | متوسط | تخفيف: Rate Limiting على المسار وfail2ban | التشغيل | مغلق (SEC-14) |
| T6 | Admin Panel | R: موظف دعم يحذف فاتورة وينكر | متوسط | تخفيف: سجل تدقيق لا يعدل يرسل إلى خادم السجلات المركزي | قائد ال Backend | مفتوح |
| T7 | السهم 3 (ال Proxy إلى API) | I وT: قراءة الطلبات أو تعديلها داخل شبكة Docker | متوسط | قبول: الخدمات في شبكة Docker معزولة على سيرفر واحد، ويراجع القرار عند نقلها إلى عدة سيرفرات | التشغيل | مقبول حتى 2027-01 |
| T8 | Email Worker | I: مفتاح API لمزود البريد يتسرب في السجلات أو المستودع فيرسل المهاجم بريداً باسم نطاقنا | مرتفع | تخفيف: المفتاح في مدير أسرار Secrets Manager وبصلاحية الإرسال فقط، وتدويره، وتنبيه عند ارتفاع الإرسال | التشغيل | مفتوح |
| T9 | Email Provider | S: رسائل مزورة باسم نطاقنا تصل إلى العملاء | متوسط | تخفيف: SPF وDKIM وسياسة DMARC بالرفض | التشغيل | مغلق |
| T10 | Admin | S: سرقة كلمة مرور موظف دعم بالتصيد Phishing | مرتفع | تخفيف: دخول موحد مع Passkeys، ولوحة الإدارة خلف VPN أو قائمة عناوين مسموحة | التشغيل | قيد التنفيذ |
لاحظ أن الجدول لا يحتوي مئتي تهديد مثل مخرج الأداة، وإنما عشرة تهديدات محددة بلغة النظام نفسه، ولكل واحد قرار ومالك وحالة، وهذا ما يجعل الجدول قابلاً للمتابعة في اجتماع الفريق الأسبوعي.
الأخطاء الشائعة
- محاولة نمذجة كل شيء مرة واحدة Boiling the Ocean: رسم كل خدمة وكل منفذ في الشركة في ورشة من يومين، ثم لا ينتهي الرسم ولا يخرج أي قرار. ابدأ بالميزة التي تعمل عليها الآن أو بالجزء الأخطر، والنموذج يكبر مع الوقت.
- نمذجة مرة واحدة قبل الإطلاق: وثيقة جميلة تحفظ في مجلد، وبعد ثلاثة أشهر لا تشبه النظام. اربط التحديث بتغيير التصميم وبطلب الدمج.
- فريق الأمن وحده: المختص الأمني لا يعرف أن خدمة البريد تقرأ من قاعدة البيانات مباشرة، والمطور يعرف. نمذجة التهديدات عمل الفريق الذي يبني النظام، وفريق الأمن يساعد ويسأل.
- الخلط بينها وبين اختبار الاختراق أو فحص الثغرات: الاختبار والفحص يعملان على نظام موجود ويجدان أخطاء التنفيذ، ونمذجة التهديدات تعمل على التصميم وتجد الضوابط الناقصة، ولا يغني أحدها عن الآخر.
- قائمة من الأداة دون فرز: نسخ مئتي نتيجة من pytm أو غيرها إلى نظام المهام يقتل ثقة الفريق بالعملية كلها. كل نتيجة تقرأ وتأخذ قراراً، والضجيج يستبعد بسبب مكتوب.
- القبول بالسكوت: تهديد بلا قرار هو تهديد مقبول دون أن يعرف أحد. القبول قرار له مالك وتاريخ مراجعة.
- النقاش في الدرجات: نصف ساعة في الخلاف على هل الاحتمال 6 أم 7 بدلاً من البحث عن تهديدات أخرى. الشبكة البسيطة تكفي.
- نسيان ما خارج الكود: النسخ الاحتياطية ولوحات الإدارة وخط CI ومفاتيح المزودين جزء من النظام، وغالباً هي أضعف ما فيه.
قائمة تحقق
- هل يوجد رسم DFD للنظام أو للميزة، فيه العناصر والأسهم وحدود الثقة، ومحفوظ في المستودع؟
- هل مررت على كل سهم يعبر حد ثقة، وعلى كل عملية تستقبله، بأسئلة STRIDE لكل عنصر؟
- إذا كان النظام يحفظ بيانات شخصية: هل سألت أسئلة الخصوصية مع LINDDUN؟
- هل لكل تهديد قرار واحد (تخفيف أو إزالة أو نقل أو قبول) ومالك؟
- هل تحولت قرارات التخفيف والإزالة إلى تذاكر، والمهمة منها إلى اختبارات تعمل في CI؟
- هل كل قبول مكتوب مع السبب وتاريخ المراجعة؟
- هل حضر الجلسة من يبني النظام ومن يشغله ومن يعرف أثره على العمل؟
- هل يوجد سبب واضح لتحديث النموذج (ميزة جديدة، مزود جديد، حادثة، ملاحظة من اختبار اختراق)، ويطلب في مراجعة طلب الدمج؟
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- نمذجة التهديدات تسأل «ما الذي قد يسوء؟» على التصميم قبل الكود، لأن الضابط الذي لم يصمم أصلاً لا تجده أدوات SAST وDAST، وOWASP تقول إن التصميم غير الآمن لا يصلحه تنفيذ مثالي.
- الطريقة أربعة أسئلة: ماذا نبني، وما الذي قد يسوء، وماذا سنفعل حياله، وهل قمنا بعمل جيد بما يكفي، وهي حلقة تعود إليها مع كل تغيير.
- الخطوة الأولى رسم DFD بخمسة رموز: الكيان الخارجي والعملية ومخزن البيانات والسهم وحد الثقة، وأغلب التهديدات تقع على الأسهم التي تعبر الحدود.
- الخطوة الثانية STRIDE: الانتحال يكسر المصادقة، والعبث يكسر السلامة، والإنكار يكسر عدم الإنكار، والكشف يكسر السرية، والحجب يكسر التوفر، ورفع الصلاحيات يكسر التفويض، وجدول STRIDE per Element يحدد الأسئلة لكل عنصر، وLINDDUN للخصوصية وPASTA للمخاطر وشجرة الهجوم لتهديد واحد مهم.
- الخطوة الثالثة قرار لكل تهديد مع مالك وتذكرة، والترتيب بالاحتمال والأثر يكفي، وDREAD لم يعد مستخدماً عند الخبراء، والتهديد المهم يصبح اختباراً.
- الأدوات: Threat Dragon للرسم بترخيص Apache-2.0، وpytm وThreagile للنموذج ككود بترخيص MIT، وأداة Microsoft مجانية لكنها مغلقة وعلى Windows فقط، وبطاقات Elevation of Privilege وCornucopia لبدء الحوار.
- في التجربة ولد pytm المخطط و238 تهديداً لتطبيق من سبع خدمات، بعضها مفيد وبعضها ضجيج لتقنيات لا نستخدمها، وبعد تسجيل القرارات والافتراضات في الملف نفسه انخفض العدد إلى 199 مع 16 تهديداً مستبعداً بسبب مكتوب.
دليلأمن سلسلة التوريد البرمجية Software Supply Chain للمبتدئينكل ما بين كود غيرك وسيرفرك قد يحمل كود المهاجم، وهنا تعرف التهديدات الحقيقية بحوادث موثقة والدفاع المناسب لكل حلقة.
دليلكيف يتذكرك الموقع بعد تسجيل الدخول؟ الجلسات وJWTبروتوكول HTTP لا يتذكر أحداً، فكيف يبقى المستخدم مسجلاً بين الصفحات، ومتى تختار الجلسة على السيرفر ومتى تختار JWT.
دليلAuthorization: كيف تجمع قرارات الصلاحيات في مكان واحدلماذا تتحول شروط الصلاحيات المتفرقة في الكود إلى ثغرات، وكيف تنقل القرار إلى محرك سياسات واحد.
دليلحماية تطبيقات الويب بطبقات أمان في ال Reverse Proxyكلمة المرور وحدها لا تحمي تطبيقاً مكشوفاً على الإنترنت، لذلك نضع أمامه طبقات حماية في ال Reverse Proxy نفسه.
دليلHSTS وCSP: ترويسات الأمان التي يحتاجها كل تطبيقما الذي تطلبه كل ترويسة أمان من المتصفح، ولماذا يصعب التراجع عن HSTS، وكيف تبني سياسة CSP لا تكسر تطبيقك.
دليلأنواع الاختبارات للمبتدئين: من Unit إلى E2E مع Playwrightما الذي يكشفه كل نوع من الاختبارات وما الذي يفوته، وكم اختباراً تكتب من كل نوع، وأين يعمل كل منها في خط CI.سجل التحديثات
- أكتوبر 2026: كتابة المقال، وتنفيذ التجربة على OWASP pytm 1.4.0 مع Python 3.12 وGraphviz 2.42.4 داخل Docker Engine 29.8، وتشغيل OWASP Threat Dragon v2.6.2 من Docker Image الرسمية، ومراجعة تراخيص الأدوات وحالتها من مستودعاتها وصفحاتها الرسمية.