سجل التعديلات (4 تحديثات، آخر تحديث 3 Sep، 2026)
سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.
- أُضيفت روابط الاستشارة الموجودة في الأصل الياباني (consultation_services). ولم يتغيّر نصّ المقالة نفسه. قراءة النسخة السابقة لهذا التحديث (DOI: 10.5281/zenodo.22240832)
- أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
- أُعيدت مراجع الأمان المحذوفة في نهاية المقال وفق الأصل الياباني.
- أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621359)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
小村 豪 (2026). قائمة التحقّق الدنيا لأمان تطبيقات Windows. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621359 https://comcomponent.com/ar/blog/2026/03/14/001-windows-app-security-minimum-checklist/
- DOI (أحدث نسخة)
- 10.5281/zenodo.21621359
- DOI (هذه النسخة)
- 10.5281/zenodo.22279763
تنزيل قائمة التحقّق بصيغة Excel
محتوى هذا الملف هو قائمة التحقّق قبل الإصدار في الفصل 4 نفسها (8 فئات و 32 بنداً). الفرق فقط وجود حقول Status و Notes، ووجود الورقتين Checklist-ja / Checklist-en باليابانية والإنجليزية. للقراءة أثناء التحقّق يكفي الفصل 4، وللتوزيع كسجل مراجعة استخدم نسخة Excel.
حديث أمان تطبيقات Windows يميل فجأة إلى الاتّساع. الثقة الصفرية، و EDR، و SBOM (Software Bill of Materials، قائمة مكوّنات البرمجيات)، وتشغيل الشهادات، وإدارة الثغرات. كلّها مهمّة، غير أنه في العمل اليومي توجد أساسيات لا تريد إسقاطها قبل ذلك.
خصوصاً في تطبيقات كهذه، سدّ تسريبات الأساس يؤثّر أكثر من «الدفاع المتقدّم» أوّلاً.
- تطبيقات سطح مكتب WPF / WinForms / WinUI
- تطبيقات Win32 بـ C++ / C#
- أدوات ربط أجهزة أو ملفات أو قواعد بيانات، وأدوات توزيع داخل المؤسسة
- تطبيقات أعمال ذات آلية تحديث تلقائي
- تكوين يشمل خدمات Windows أو ملفات EXE مساعدة
في تطوير تطبيقات Windows، الواقع أقرب إلى عدم إبقاء ثقب واضح الخطر أوّلاً، لا إلى إكمال كل شيء دفعة واحدة. هنا نرتّب، بترتيب التصميم ثم التنفيذ ثم التوزيع ثم التشغيل، النقاط التي لا تريد إسقاطها في حدّها الأدنى، بشكل يسهل التحقّق منه.
flowchart TB
accTitle: مسار هذه المقالة
accDescr: بسياسة سدّ تسريبات الأساس قبل الدفاع المتقدّم، ترتّب المقالة النقاط الدنيا بترتيب التصميم ثم التنفيذ ثم التوزيع ثم التشغيل.
adv1["دفاع متقدّم (الثقة الصفرية وغيره)"] -.->|"قبل ذلك"| base1["سدّ تسريبات الأساس"]
base1 --> o1["التصميم"]
o1 --> o2["التنفيذ"]
o2 --> o3["التوزيع"]
o3 --> o4["التشغيل"]
الشكل 1: بسياسة سدّ تسريبات الأساس قبل الدفاع المتقدّم، ننظر بترتيب التصميم ثم التنفيذ ثم التوزيع ثم التشغيل.
1. الخلاصة أوّلاً
- ما لا تريد إسقاطه أوّلاً: عدم طلب امتيازات مدير غير لازمة، والتوقيع، وعدم حفظ الأسرار بنصّ صريح، وعدم تعطيل التحقّق من الشهادات.
- تطبيق Windows يصير مادّة التوزيع نفسها سطح هجوم. أأمن أن تنظر حتى EXE / DLL / MSI / MSIX / مكوّن التحديث التلقائي.
ServerCertificateValidationCallback => true، وسلاسل الاتّصال بنصّ صريح، والتحميل الفضفاض بـLoadLibrary("foo.dll")، وتنفيذ SQL بربط سلاسل، بنود ينبغي تجنّبها حتى على الخطّ الأدنى.- إن كانت المعالجة التي تحتاج امتيازات مدير جزءاً فقط، فأأمن فصل ذلك الجزء إلى EXE أو خدمة منفصلة بدل رفع التطبيق كلّه.
- التطبيق الذي يُوزَّع على Windows أفضل أن يُفكَّر فيه بافتراض التوقيع + طابع زمني. لا يفيد ثقة المستخدم فقط، بل يسهّل أيضاً كشف العبث والشرح التشغيلي.
- الأسرار عند الحفظ تُوزَّع حسب الاستخدام بين DPAPI / ProtectedData و Credential Locker. على الأقل تريد الخروج من وضعها بنصّ صريح في
appsettings.json. - السجلات ليست أفضل كلّما كثرت. إن أبقيت الرموز وكلمات المرور وسلاسل الاتّصال والبيانات الشخصية ومتن الطلب كاملاً كما هي، صارت السجلات نفسها بطلة الحادث.
الأمان الأدنى ليس إضافة ميزة خاصّة بقدر ما هو عدم إبقاء سلوك افتراضي خطر أو تنفيذ فضفاض.
flowchart TB
accTitle: فكرة الأمان الأدنى
accDescr: الأمان الأدنى ليس إضافة ميزة خاصّة، بل عدم إبقاء سلوك افتراضي خطر أو تنفيذ فضفاض.
add1["إضافة ميزة خاصّة"] -.->|"ليس محور الأدنَى"| goal1["الأمان الأدنى"]
rm1["عدم إبقاء سلوك افتراضي خطر أو تنفيذ فضفاض"] -->|"هذا هو المحور"| goal1
الشكل 2: الخطّ الأدنى ليس إضافة ميزة، بل عدم إبقاء سلوك افتراضي خطر أو تنفيذ فضفاض.
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 21، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. نطاق هذه المقالة ومعنى «الأدنى»
2.1. النطاق المستهدف
تطبيقات Windows التي تفترضها هذه المقالة من هذا النوع.
- تطبيقات سطح مكتب WPF / WinForms / WinUI
- تطبيقات Win32 بـ C++ / C#
- أدوات توزيع داخل المؤسسة، وأدوات ربط أجهزة، وأدوات مراقبة
- تكوين يشمل EXE مساعداً وخدمات Windows ومحدِّثاً
- برمجيات أعمال تُوزَّع كـ EXE / MSI / MSIX
«الأدنى» هنا ليس الشكل النهائي الذي يمرّ في التدقيق، بل بنود إن سقطت وقع الحادث بشكل عادي.
flowchart TB
accTitle: معنى «الأدنى» في هذه المقالة
accDescr: الأدنى في هذه المقالة لا يشير إلى الشكل النهائي الذي يمرّ في التدقيق، بل إلى بنود إن سقطت وقع الحادث بشكل عادي.
au1["الشكل النهائي الذي يمرّ في التدقيق"] -.->|"لا يشير إليه هنا"| mn1["«الأدنى» في هذه المقالة"]
ac1["بنود إن سقطت وقع الحادث بشكل عادي"] -->|"هذا ما يشير إليه"| mn1
الشكل 3: ما يشير إليه «الأدنى» ليس الشكل النهائي للتدقيق، بل البنود التي إن سقطت وقع الحادث بشكل عادي.
نوحّد أيضاً مقدّمات أمثلة الشيفرة. أمثلة C# تفترض .NET 8 فما بعد، وأمثلة C++ تفترض Win32 API. في .NET Framework 4.8 تبقى طريقة التفكير نفسها، غير أن الكتابة الموصى بها تغيّرت في مواضع. المثال الأبرز محيط ServicePointManager في 3.6، حيث صارت المقدّمة في الشيفرة الجديدة استخدام IHttpClientFactory و HttpClient. عند إعادة قراءة كود جيل أقدم انتبه إلى هذا الموضع فقط.
2.2. خارج النطاق
في المقابل نخرج من مركز هذه المقالة أموراً أيضاً.
- تصميم الثقة الصفرية للمؤسسة كلّها
- التشغيل الكلّي لـ EDR / SIEM / DLP / MDM
- تصليب مفصّل لبرامج تشغيل النواة
- حديث تصميم التشفير من الصفر
- تحليل تهديد متقدّم أو إجراءات الطبّ الشرعي
أي أننا لا نتناول «سياسات أمان مؤسسية ضخمة»، بل خطّاً أساسياً يصعب على مطوّر تطبيقات Windows إسقاطه بنفسه قبل الإصدار.
flowchart TB
accTitle: رسم خطّ الداخل والخارج
accDescr: السياسات المؤسسية الضخمة خارج النطاق، والخطّ الأساسي الذي يصعب على مطوّر تطبيقات Windows إسقاطه بنفسه قبل الإصدار هو الداخل.
org1["سياسات مؤسسية ضخمة"] -.->|"لا نتناولها هنا"| sc1["نطاق هذه المقالة"]
dev1["خطّ أساسي يصعب على المطوّر إسقاطه بنفسه"] -->|"هذا ما نتناوله"| sc1
الشكل 4: ما نتناوله ليس سياسات المؤسسة كلّها، بل الخطّ الأساسي الذي يستطيع المطوّر ضبطه بنفسه قبل الإصدار.
3. قائمة تحقّق للنظر أوّلاً
قبل النقاش التفصيلي نضع جدولاً يرى الصورة كلّها. هذا وحده يكفي لتحديد مواضع المراجعة.
3.1. الصورة الكاملة
| البند | الحدّ الأدنى | الرفض النمطي |
|---|---|---|
| صلاحيات التشغيل | الأساس asInvoker، وفصل المعالجة التي تحتاج رفعاً فقط |
جعل التطبيق كلّه requireAdministrator |
| موثوقية مادّة التوزيع | توقيع شيفرة لـ EXE / DLL / MSI / MSIX مع طابع زمني | التوزيع بلا توقيع |
| التحديث | تثبيت مصدر التحديث وكشف العبث عبر HTTPS والتحقّق من التوقيع | التنزيل عبر HTTP ثم الكتابة فوق الملف كما هو |
| المعلومات السرّيّة | عدم وضع سرّ في الشيفرة أو إعداد نصّ صريح، واستخدام DPAPI / Credential Locker وغيرهما | وضع مفتاح API أو سلسلة اتّصال بنصّ صريح في ملف الإعداد |
| الاتّصال | استخدام HTTPS وعدم تعطيل التحقّق من الشهادات | تخطّي التحقّق من الشهادات دائماً بـ return true |
| الإدخال الخارجي | التحقّق من SQL والملفات و IPC و URI و CSV و JSON وغيرها كلّها | التمرير كما هو لأنّه «أداة داخلية» |
| تحميل DLL | مسار مطلق، و SetDefaultDllDirectories، وترتيب بحث آمن |
إسناد LoadLibrary("foo.dll") إلى الدليل الحالي |
| السجلات | إخفاء الرموز وكلمات المرور و PII، وفصل أخطاء المستخدم | عرض تفاصيل الاستثناء أو سلاسل الاتّصال وحفظها كما هي |
| التبعيات | تحديث مستمر لـ SDK و NuGet ووقت تشغيل VC++ وتبعيات OSS | التثبيت لسنوات دون تتبّع معلومات الثغرات |
3.2. الأساس في الصلاحيات هو asInvoker
هذا أوّل ما تريد مراجعته في تطبيق Windows. إن شغّلت التطبيق كلّه بامتيازات مدير، نُفِّذت الأخطاء واستبدال DLL وسوء قراءة ملف الإعداد وعيوب الإدخال الخارجي بالصلاحيات القوية كما هي.
flowchart TB
accTitle: خطر جعل التطبيق كلّه بامتيازات مدير
accDescr: إن شغّلت التطبيق كلّه بامتيازات مدير، نُفِّذت الأخطاء واستبدال DLL وسوء قراءة ملف الإعداد وعيوب الإدخال الخارجي بالصلاحيات القوية كما هي.
all1["تشغيل التطبيق كلّه بامتيازات مدير"] --> bug1["أخطاء"]
all1 --> swp1["استبدال DLL"]
all1 --> inp1["سوء قراءة إعداد وعيوب إدخال"]
bug1 --> pw1["تُنفَّذ كما هي بصلاحيات قوية"]
swp1 --> pw1
inp1 --> pw1
الشكل 5: رفع التطبيق كلّه يجعل كلّ عيب يحمله يُنفَّذ بصلاحيات قوية.
الاتجاه الأساسي كالتالي.
- تطبيق واجهة المستخدم العادي
asInvoker - فصل المعالجة التي تحتاج امتيازات مدير إلى عملية أو خدمة منفصلة
- إجراء الرفع في اللحظة اللازمة فقط
- التحقّق أيضاً من الإدخال الممرَّر إلى EXE المساعد أو الخدمة
إن كان تطبيق سطح المكتب للعرض والتحرير فقط عادة، ولم تلزم امتيازات المدير إلا للتثبيت أو تغيير إعداد جدار الحماية، فأأمن جمع الجزء الذي يحتاج رفعاً في وسيط (broker) بدل جعل التطبيق كلّه requireAdministrator.
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
«أسهل إن عمل كمدير» يظهر أثره لاحقاً في الغالب. التشغيل بأقلّ صلاحية، وعزل العمليات اللازمة رغم ذلك، يصغّر نصف قطر الحادث كثيراً.
flowchart TB
accTitle: فصل المعالجة التي تحتاج رفعاً فقط
accDescr: تطبيق واجهة المستخدم العادي يعمل بـ asInvoker، وتُجمع المعالجة التي تحتاج امتيازات مدير في وسيط عملية أو خدمة منفصلة، ويُجرى الرفع في اللحظة اللازمة فقط.
ui1["تطبيق الواجهة (asInvoker)"] -->|"طلب في اللحظة اللازمة فقط"| br1["وسيط (EXE / خدمة منفصلة)"]
br1 --> el1["تنفيذ المعالجة التي تحتاج رفعاً فقط"]
ui1 -.-> vd1["التحقّق أيضاً من الإدخال الممرَّر إلى الوسيط"]
الشكل 6: التشغيل العادي بـ asInvoker وجمع ما يحتاج رفعاً في وسيط يصغّر نصف قطر الحادث.
3.3. توقيع الثنائيات والمُثبِّت
على Windows موثوقية مادّة التوزيع هي ما يحسم. ما يلمسه المستخدم ليس الشيفرة المصدرية، بل EXE و DLL و MSI و MSIX والمحدِّث. إن بقيت بلا توقيع ضعفت الشرح التشغيلي وكشف العبث والاطمئنان عند التوزيع.
flowchart TB
accTitle: ما يلمسه المستخدم هو مادّة التوزيع
accDescr: ما يلمسه المستخدم ليس الشيفرة المصدرية بل EXE و DLL والمُثبِّت والمحدِّث، وإن بقيت بلا توقيع ضعف كشف العبث والشرح التشغيلي.
us1["ما يلمسه المستخدم"] --> bin1["EXE / DLL"]
us1 --> pkg1["MSI / MSIX"]
us1 --> upd1["المحدِّث"]
bin1 --> ns1["بلا توقيع يضعف كشف العبث والشرح"]
pkg1 --> ns1
upd1 --> ns1
الشكل 7: سطح الهجوم هو مادّة التوزيع نفسها، وبقاؤها بلا توقيع يفقد سند الموثوقية.
في الحدّ الأدنى، انظر إلى ما يلي.
- توقيع EXE / DLL / MSI / MSIX
- توقيع الثنائيات المساعدة المستخدمة في التحديث أيضاً، لا المُثبِّت فقط
- إرفاق طابع زمني
- إدخال أجل الشهادة وإجراء التجديد في خطوات الإصدار
التوقيع بلا طابع زمني خصوصاً يتعثّر في التحقّق بعد انتهاء أجل الشهادة. لا تنهِ الأمر عند «وُقِّع إذن انتهى»، بل أدخل التوقيع + طابع زمني حتى خطوات الإصدار فيصير أثبت.
flowchart TB
accTitle: التوقيع والطابع الزمني
accDescr: التوقيع بلا طابع زمني يتعثّر في التحقّق بعد انتهاء أجل الشهادة، وإدخال التوقيع والطابع الزمني حتى خطوات الإصدار يثبت الأمر.
sg2["توقيع فقط"] --> tr1["يتعثّر التحقّق بعد انتهاء الأجل"]
ts1["توقيع + طابع زمني"] --> st2["أقلّ تعثّراً بعد انتهاء الأجل"]
ts1 -.-> fl1["أدخله في خطوات الإصدار"]
الشكل 8: لا تنهِ عند التوقيع وحده، وأدخل الطابع الزمني أيضاً في خطوات الإصدار.
إن استخدمت MSIX فتوقيع الحزمة مقدّمة. وحتّى مع توزيع MSI / EXE، الأفضل على الأقل توقيع متن المُثبِّت والثنائيات التنفيذية الرئيسة.
3.4. تثبيت مسار التحديث وإدخال كشف العبث
في تطبيقات Windows اليوم، مسار التحديث يُستخدم أطول من التثبيت الأوّل. إن كان فضفاضاً، حتى لو بُنيت النواة بعناية، صار المحدِّث أضعف موضع.
flowchart TB
accTitle: مسار التحديث هو الأطول استخداماً
accDescr: مسار التحديث يُستخدم أطول من التثبيت الأوّل، فإن كان محيط التحديث فضفاضاً صار المحدِّث أضعف موضع في التطبيق كلّه.
ins1["التثبيت الأوّل"] -.->|"يُستخدم مرّة"| ap1["عمر التطبيق"]
up2["مسار التحديث"] -->|"يُستخدم طويلاً"| ap1
up2 --> wk2["إن كان فضفاضاً صار الأضعف"]
الشكل 9: مسار التحديث يُستخدم أطول من التثبيت الأوّل، وبناؤه فضفاضاً يجعله أكبر نقطة ضعف.
حول التحديث خمس نقاط تريد التفكير فيها في الحدّ الأدنى.
- جلب ملف التحديث بافتراض HTTPS
- التحقّق من توقيع أو هاش مادّة التحديث بعد التنزيل
- منع استبدال عنوان مصدر التحديث بلا قيد من الشيفرة أو الإعداد
- توقيع مكوّن التحديث نفسه أيضاً
- تحديد التراجع أو إجراء الاستعادة عند الفشل
إن أمكن اعتماد MSIX + App Installer، يسهل تقريب آلية التحديث نحو نظام التشغيل. أمّا إن حملت محدِّثاً خاصّاً، فلزم التحقّق من أمان الاتّصال و أصالة مادّة التوزيع كليهما. HTTPS وحده يحمي «مسار الاتّصال» ولا يضمن «أن هذا الملف صادر فعلاً عنك».
flowchart TB
accTitle: أمران يُتحقَّق منهما في التحديث
accDescr: المحدِّث الخاص يحتاج التحقّق من أمان الاتّصال عبر HTTPS ومن الأصالة عبر توقيع مادّة التحديث المنزَّلة أو هاشها.
dl1["جلب ملف التحديث عبر HTTPS"] --> vf1["التحقّق من التوقيع أو الهاش"]
vf1 --> ap2["تطبيق ما اجتاز التحقّق فقط"]
dl1 -.-> lim1["HTTPS يحمي مسار الاتّصال فقط"]
vf1 -.-> own1["التثبّت من أنه صادر عنك"]
الشكل 10: الجلب عبر HTTPS لا يحسم الأصالة؛ طبّق بعد اجتياز التحقّق من التوقيع أو الهاش.
3.5. عدم وضع الأسرار في الشيفرة أو إعداد نصّ صريح
هذا موضع تقع فيه الحوادث فعلاً في العمل اليومي. بعبارات «أداة داخلية» أو «نوزّع exe فقط على أي حال»، تُوضَع سلاسل الاتّصال ومفاتيح API وبيانات اعتماد المجلد المشترك والرموز الثابتة في الشيفرة أو ملف الإعداد.
في الحدّ الأدنى تريد تجنّب هذه الأشكال.
- مفتاح API مكتوب مباشرة في الشيفرة
- كلمة مرور نصّ صريح في
appsettings.jsonأوapp.config - سلسلة اتّصال دخلت المستودع
- تصميم يضع مفتاح فكّ التشفير والنصّ المشفَّر في الموضع نفسه
- بيانات اعتماد ثابتة مشتركة للجميع لا لكل مستخدم
الخيارات الواقعية في تطبيق Windows تقريباً هذه الأربعة.
- تريد حفظ بيانات اعتماد Windows في تطبيق سطح مكتب محزوم / عائلة WinUI انظر إلى Credential Locker
- تريد حفظ سرّ مشفَّراً محليّاً
في Win32 / .NET استخدم DPAPI /
ProtectedData - جهة الاتّصال تستطيع مصادقة Windows أو المصادقة المتكاملة إن أمكن فلا تحمل كلمة مرور في التطبيق
- يمكن إدارة السرّ في السحابة أو جهة الخادم فضّل تصميماً لا يضمّن سرّاً طويل الأجل في العميل
في C#، مجرّد استخدام DPAPI كما يلي أفضل بكثير من الحفظ بنصّ صريح. ذهاباً وإياباً بين الحفظ والقراءة يصير كالتالي.
// C# / .NET 8。ProtectedData は Windows 専用で、
// .NET では NuGet パッケージ System.Security.Cryptography.ProtectedData が必要です。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class SecretStore
{
// 復号時にも同じ値が要る。null でも動くが、付けたほうが安全。
private static readonly byte[] Entropy = [0x4b, 0x53, 0x2d, 0x76, 0x31];
public static void Save(string path, string secretText)
{
byte[] plaintext = Encoding.UTF8.GetBytes(secretText);
byte[] ciphertext = ProtectedData.Protect(
plaintext,
Entropy,
DataProtectionScope.CurrentUser);
// バイト列のままファイルへ置いてもよいが、設定ファイルへ入れるなら Base64 にする。
File.WriteAllText(path, Convert.ToBase64String(ciphertext));
CryptographicOperations.ZeroMemory(plaintext);
}
public static string Load(string path)
{
byte[] ciphertext = Convert.FromBase64String(File.ReadAllText(path));
// 保存したときと同じユーザー、同じ entropy でなければ CryptographicException になる。
byte[] plaintext = ProtectedData.Unprotect(
ciphertext,
Entropy,
DataProtectionScope.CurrentUser);
try
{
return Encoding.UTF8.GetString(plaintext);
}
finally
{
CryptographicOperations.ZeroMemory(plaintext);
}
}
}
طرف الاستدعاء يصير كالتالي.
string path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"SampleApp",
"token.dat");
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
SecretStore.Save(path, "example-api-key");
string restored = SecretStore.Load(path);
المهم هنا ليس «شفّرنا إذن آمن»، بل حسم من يستطيع فكّ التشفير في التصميم.
المعنى يتغيّر كثيراً بين CurrentUser و LocalMachine. CurrentUser لا يفكّ إلا المستخدم الذي حفظ، و LocalMachine يستطيع أيّ شخص على الجهاز نفسه فكّ التشفير. إن شغّلت كخدمة بحساب آخر، أو كان هناك تشغيل بتبديل المستخدم، فبدون حسم هذا أوّلاً تتعثّر لاحقاً عند «لا يُقرأ» أو «يُقرأ بلا قصد».
flowchart TB
accTitle: من يستطيع فكّ التشفير في DPAPI
accDescr: نطاق DPAPI إن كان CurrentUser لا يفكّ إلا المستخدم الذي حفظ، وإن كان LocalMachine يستطيع أيّ شخص على الجهاز نفسه فكّ التشفير، لذا يلزم حسم من يستطيع فكّ التشفير في التصميم أوّلاً.
dc1["حسم من يستطيع فكّ التشفير في التصميم"] -->|"CurrentUser"| cu1["المستخدم الحافظ فقط"]
dc1 -->|"LocalMachine"| lm1["أيّ شخص على الجهاز نفسه"]
dc1 -.-> lt1["بدون الحسم تتعثّر لاحقاً"]
الشكل 11: DPAPI يغيّر نطاق من يستطيع فكّ التشفير حسب اختيار النطاق، فاحسم «من يستطيع فكّ التشفير» أوّلاً.
لاحظ أن DPAPI يحمل المفتاح في ملف تعريف المستخدم، والتوثيق يصرّح بأن فكّ التشفير يفشل إن لم يكن الملف محمَّلاً (أثناء impersonation مثلاً). إن فكّرت في الاستخدام من خدمة فتثبّت من هذه النقطة أيضاً.
flowchart TB
accTitle: مفتاح DPAPI وملف تعريف المستخدم
accDescr: DPAPI يحمل المفتاح في ملف تعريف المستخدم، لذا يفشل فكّ التشفير إن لم يكن الملف محمَّلاً كما أثناء impersonation.
ky1["مفتاح DPAPI"] --> pf1["في ملف تعريف المستخدم"]
pf1 -->|"الملف غير محمَّل (أثناء impersonation وغيره)"| fe1["يفشل فكّ التشفير"]
fe1 -.-> sv2["يلزم التثبّت عند الاستخدام من خدمة"]
الشكل 12: مفتاح DPAPI في ملف تعريف المستخدم، ولا يمكن فكّ التشفير إن لم يكن الملف محمَّلاً.
لاتّصال SQL Server، قد تكون مصادقة Windows المرشّح الأوّل في بيئة محلية.
إن اضطررت لتضمين بيانات اعتماد في سلسلة الاتّصال، فأبقى على الأقل Persist Security Info=False، ولا تتركها في ملف إعداد نصّ صريح.
3.6. الاتّصال بافتراض HTTPS، ولا تقتل التحقّق من الشهادات
ثغرة وُضعت بنيّة التطوير فقط تبقى في الإنتاج كما هي. حوادث محيط الاتّصال تقريباً بهذا النمط.
ما يبقى بسهولة في مادّة الشحن شيفرة أو إعداد من هذا النوع.
ServicePointManager.ServerCertificateValidationCallback += ... => trueHttpClientHandler.DangerousAcceptAnyServerCertificateValidator- الشحن مع تعطيل التحقّق من إلغاء الشهادة
- إبقاء شيفرة تفترض شهادة موقَّعة ذاتياً للتطوير في الإنتاج
الاتجاه الأدنى بسيط.
- اتّصال الإنتاج HTTPS
- عدم تخطّي التحقّق من الشهادات دائماً
- إن لزم تخفيف استثنائي للتحقّق فـ قيّد المضيف المستهدف والشهادة
- استبعد شيفرة التجاوز التطويرية حتماً بشرط بناء أو إعداد
- في .NET انتبه أيضاً إلى التحقّق من الإلغاء
المثال السيّئ تقريباً كالتالي.
ServicePointManager.ServerCertificateValidationCallback +=
(_, _, _, _) => true;
يبدو مريحاً، غير أن الحركة تقترب من «هذا الاتّصال HTTPS يمرّ لأيّ طرف». إن نزعت التحقّق من الشهادات، حتى مع HTTPS يصير المحتوى مفرَّغاً جدّاً.
الكتابة تتغيّر حسب جيل .NET المفترض
وضع إعداد عام على ServicePointManager كتابة عهد .NET Framework. في الشيفرة الجديدة أوضح تلقّي HttpClient من IHttpClientFactory، وإن لزم إعداد حول TLS فاحمله في جهة SocketsHttpHandler أو HttpClientHandler.
غير أن ترك الأمر بعبارة «واجهة قديمة إذن لم تعد تؤثّر» خطر. توثيق Microsoft يذكر أن ServicePointManager.ServerCertificateValidationCallback يُربَط منذ .NET 9 بـ RemoteCertificateValidationCallback في SocketsHttpHandler.SslOptions. أي أن سطر => true واحد في موضع ما قد يمرّر حتى اتّصال HttpClient.
flowchart TB
accTitle: مسار تأثير الاستدعاء الراجع القديم على الاتّصال الحالي
accDescr: ServerCertificateValidationCallback في ServicePointManager يُربَط منذ .NET 9 باستدعاء التحقّق في SocketsHttpHandler، لذا قد يمرّر سطر true واحد حتى اتّصال HttpClient.
old1["استدعاء التحقّق في ServicePointManager"] -->|".NET 9 فما بعد يُربَط"| new1["التحقّق في جهة SocketsHttpHandler"]
new1 --> ef1["يؤثّر أيضاً في اتّصال HttpClient"]
ef1 -.-> rk2["سطر true واحد قد يفرّغ الاتّصال كلّه"]
الشكل 13: تخطّي التحقّق الموضوع على واجهة قديمة قد يمرّ عبر الربط حتى اتّصال HttpClient الحالي.
إن أردت تخفيف التحقّق استثناءً، فلا تستخدم إعداداً عامّاً يؤثّر في العملية كلّها، بل شكلاً مغلقاً على ذلك المعالج فقط.
// C# / .NET 8. مثال يستثني مضيفاً وشهادة معيّنين فقط.
// حتى إن رخّصت للتطوير، إن لم تحدّد الهدف فهو كقتل التحقّق عالمياً.
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
// بصمة إبهام الشهادة المستهدفة. يجوز قراءتها من الإعداد.
const string ExpectedThumbprint = "2B0C4E6A8D1F3B5D7F9A1C3E5A7C9E1B3D5F7A91";
var handler = new HttpClientHandler
{
CheckCertificateRevocationList = true, // انظر في الإلغاء. الافتراضي false
ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) =>
{
if (errors == SslPolicyErrors.None)
{
return true;
}
// ما يُقبل نقطة واحدة فقط: «هذه المحطة لا تثق بـ CA الداخلي».
// عدم وصول الشهادة أو عدم تطابق اسم المضيف لا يُقبلان
if (errors != SslPolicyErrors.RemoteCertificateChainErrors)
{
return false;
}
// ثبِّت الطرف والشهادة
if (request.RequestUri?.Host != "device.internal.example"
|| certificate is null
|| !string.Equals(certificate.Thumbprint, ExpectedThumbprint,
StringComparison.OrdinalIgnoreCase))
{
return false;
}
// حتى الشهادة المُثبَّتة واحدة لا يُقبل انتهاء أجلها ولا إلغاؤها.
// لحظة القبول يصير مساراً يستمر في استخدام «شهادة أُلغيت لأن المفتاح تسرّب»
return chain is not null
&& chain.ChainStatus.All(s =>
s.Status is X509ChainStatusFlags.NoError
or X509ChainStatusFlags.UntrustedRoot
or X509ChainStatusFlags.PartialChain);
},
};
using var client = new HttpClient(handler);
ExpectedThumbprint تُعطى كقيمة ثابتة أو من إعداد، وهي بصمة الشهادة المستهدفة.
المهم هنا ألّا تتجاهل errors و chain.ChainStatus. إن أعدت true لأن البصمة تطابقت، مرّ الأمر حتى إن انتهت صلاحية تلك الشهادة، أو حتى بعد تسريب المفتاح وإلغائها. التثبيت يعني «نثق بهذه الشهادة الواحدة فقط»، لا «نثق بهذه الواحدة مهما حدث». ما تسمح به الشيفرة أعلاه هو UntrustedRoot و PartialChain فقط (أي أن مرجع التصديق الداخلي غير مثبَّت على هذا الجهاز)، و NotTimeValid (انتهاء الأجل) و Revoked (الإلغاء) يُرفضان كما هما.
لرؤية الإلغاء يلزم CheckCertificateRevocationList = true (الافتراضي false ولا يُتحقَّق من الإلغاء). في المقابل، إن كان مرجع التصديق الداخلي لا ينشر CRL ولا OCSP، يُرفَض بـ RevocationStatusUnknown. وهذا السلوك الصحيح. إن تعذّر تجهيز وسيلة التحقّق من الإلغاء فاملأ الفجوة إمّا بـ تقصير مدّة صلاحية الشهادة أو بـ صنع مسار لإعادة توزيع القيمة المُثبَّتة مسبقاً. أخطر حالة هي «تثبيت شهادة طويلة الأجل دون القدرة على إلغائها».
flowchart TB
accTitle: تدفّق الحكم عند التثبيت
accDescr: بلا أخطاء يُمرَّر، وما عدا خطأ السلسلة يُرفَض، ويُثبَّت اسم المضيف والبصمة، وفي حالة السلسلة أيضاً يُرفَض انتهاء الأجل والإلغاء.
e0["فحص errors"] -->|"None"| pass1["تمرير"]
e0 -->|"غير خطأ السلسلة"| rj1["رفض"]
e0 -->|"خطأ السلسلة فقط"| hchk["مطابقة اسم المضيف والبصمة"]
hchk -->|"عدم تطابق"| rj1
hchk -->|"تطابق"| cchk["فحص حالة السلسلة"]
cchk -->|"انتهاء أجل أو إلغاء وغيرهما"| rj1
cchk -->|"حالة عدم إدخال مرجع التصديق الداخلي فقط"| pass1
الشكل 14: حتّى مع التثبيت لا تتجاهل، فتدفّق الحكم يرفض انتهاء الأجل والإلغاء كما هما.
3.7. معاملة الإدخال الخارجي كلّه كـ «إدخال غير موثوق»
تطبيق Windows ليس تطبيق ويب، فيميل التحقّق من الإدخال إلى التساهل. غير أن مداخل الإدخال الخارجي عمليّاً أكثر ممّا يُظَنّ.
- مسار ملف
- CSV / Excel / JSON / XML
- وسائط سطر الأوامر
- named pipe / socket / COM / RPC / gRPC
- نصّ يُمرَّر إلى قاعدة البيانات
- قيم السجلّ
- الحافظة
- URL / deep link
- بيانات تعود من جهاز خارجي أو SDK
ما لا تريد إسقاطه خصوصاً في الحدّ الأدنى ثلاثة.
- اجعل SQL مُعلَّم المعاملات حتماً لا تركّب SQL بربط سلاسل.
- طبّع مسار الملف ثم استخدمه لا تستخدم مساراً يحدّده المستخدم كما هو للحذف أو الكتابة فوقه أو فكّه.
- ضع حدّ حجم وفحص صيغة عند قراءة ملف خارجي «فُتح إذن آمن» ليس صحيحاً.
في مثال SQL، تجنّب هذا.
var sql = "SELECT * FROM Users WHERE Name = '" + userName + "'";
حتّى في الحدّ الأدنى أزِح نحو هذا.
using System.Data;
using Microsoft.Data.SqlClient;
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT * FROM Users WHERE Name = @name";
cmd.Parameters.Add("@name", SqlDbType.NVarChar, 256).Value = userName;
«أداة داخلية إذن الإدخال موثوق» مقدّمة خطرة جدّاً. عمليّاً يدخل CSV تالف، واسم ملف غير متوقَّع، وبيانات قاعدة قديمة، وخطأ إدخال يدوي من المشغّل، و JSON ناقص كتبته أداة أخرى، بشكل عادي.
flowchart TB
accTitle: حتّى الأداة الداخلية يصلها إدخال تالف
accDescr: حتّى الأداة الداخلية يصلها بشكل عادي CSV تالف واسم ملف غير متوقَّع وبيانات قاعدة قديمة وخطأ إدخال يدوي و JSON ناقص، لذا يُعامَل الإدخال الخارجي كلّه كإدخال غير موثوق.
csv1["CSV تالف"] --> in2["إدخال إلى التطبيق"]
fn1["اسم ملف غير متوقَّع"] --> in2
hm1["خطأ إدخال يدوي وبيانات قديمة"] --> in2
in2 --> tr2["التحقّق كإدخال غير موثوق كلّه"]
الشكل 15: حتّى الأداة الداخلية يصلها إدخال تالف بشكل عادي، فتحقّق عند كل مدخل ثم استخدم.
3.8. لا تترك مصدر تحميل DLL غامضاً
هذا فخّ على طابع Windows.
إن حمّلت DLL بالاسم وحده مثل LoadLibrary("foo.dll")، فقد تلتقط DLL من موضع غير مقصود حسب ترتيب البحث.
ما يُفعَل محدَّد.
- إن أمكن فحدّد المسار المطلق لـ DLL
- اضبط
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)في مرحلة مبكّرة - أضف هدف البحث صراحة بـ
AddDllDirectory - تجنّب تصميماً يمرّر نتيجة
SearchPathكما هي إلىLoadLibrary - لا تعتمد كلّ الاعتماد على safe DLL search mode
مثلاً في شيفرة أصلية، إدخال التالي في مرحلة مبكّرة من تهيئة العملية خيار قوي.
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
ثم سجّل الأدلّة الإضافية اللازمة فقط بـ AddDllDirectory.
هذا الموضع يُترَك بسهولة لأنه «يعمل عادة»، غير أنه ينكسر بهدوء إن تغيّر دليل العمل في جهة التوزيع، أو دخلت DLL منتج آخر إلى PATH. يؤثّر لا في الأمان فقط، بل أيضاً كوقاية من الأعطال.
flowchart TB
accTitle: تثبيت مصدر تحميل DLL
accDescr: التحميل بالاسم وحده قد يلتقط DLL من موضع غير مقصود حسب ترتيب البحث؛ فاضبط SetDefaultDllDirectories مبكّراً، وأضف هدف البحث صراحة بـ AddDllDirectory، وإن أمكن فحدّد مساراً مطلقاً.
nm1["التحميل بالاسم وحده"] --> pick2["التقاط DLL غير مقصودة حسب ترتيب البحث"]
fix1["SetDefaultDllDirectories مبكّراً"] --> add2["إضافة صريحة بـ AddDllDirectory"]
add2 --> abs1["مسار مطلق إن أمكن"]
abs1 --> safe1["مصدر التحميل لا يبقى غامضاً"]
الشكل 16: أوقف التحميل بالاسم وحده، وصرّح بهدف البحث وثبّت مصدر التحميل.
3.9. لا تخرج أسراراً في السجلات والاستثناءات
زيادة السجلات لأجل التحقيق في الأعطال مهمّة. غير أن السجلات تصير أيضاً بسهولة مقبرة للأسرار.
ما تريد مراجعته في الحدّ الأدنى حول السجلات:
- لا تخرج كلمات المرور أو Bearer token أو مفاتيح API إلى السجلّ
- لا تخرج سلسلة الاتّصال كاملة
- أخفِ البيانات الشخصية ومتن بيانات الأعمال
- افصل تفاصيل الاستثناء بين شاشة المستخدم والسجلّ الداخلي
- لا تفعّل سجلات PII الخاصّة بالتصحيح في الإنتاج
- راجع صلاحيات موضع حفظ dump و trace
في .NET الحديث صار الترتيب بافتراض الإخفاء (redaction) أسهل أيضاً. على الأقل تريد إيقاف «حوّل كل شيء إلى نصّ وسجّله كما هو».
إخفاقات شائعة:
- حفظ متن طلب / استجابة HTTP كاملاً
- إخراج الرمز أو الترويسة كلّها عند فشل المصادقة
- عرض رسالة الاستثناء كما هي في MessageBox
- تضمين سجلات سرّيّة كلّها في ZIP للصيانة
عرض الخطأ مثلاً يُفصَل كالتالي.
- للمستخدم: «فشل الاتّصال بالخادم. راجع إعداد الشبكة و URL.»
- السجلّ الداخلي: المضيف الذي فشل، نوع خطأ TLS، معرّف الارتباط، stack trace، عدد إعادة المحاولة
هذا الفصل وحده يحسّن كثيراً توازن منع التسرّب وقابلية التحقيق.
flowchart TB
accTitle: فصل عرض الخطأ
accDescr: عند حدوث خطأ يُخرَج للمستخدم توجيه موجز فقط، ويُبقى في السجلّ الداخلي معلومات التحقيق مثل المضيف الذي فشل ونوع الخطأ ومعرّف الارتباط و stack trace.
er1["حدوث خطأ"] --> usr1["للمستخدم: توجيه موجز فقط"]
er1 --> lg2["السجلّ الداخلي: مضيف ونوع ومعرّف ارتباط وغيرها"]
lg2 -.-> bl1["منع التسرّب مع الإبقاء على قابلية التحقيق"]
الشكل 17: فصل عرض المستخدم عن السجلّ الداخلي وحده يحسّن توازن التسرّب وقابلية التحقيق.
3.10. لا تترك مكتبات التبعية وأدوات التطوير بلا متابعة
أخيراً بند هادئ وكبير الأثر. حتى إن بُنيت نواة التطبيق بعناية، فإن إبقاء وقت تشغيل قديم أو مكتبة تبعية ذات ثغرة معروفة يفرّغ الأساس.
البنود نفسها ليست كثيرة.
- إبقاء .NET SDK / runtime ضمن إصدار مدعوم
- مراجعة تحديثات تبعيات NuGet / OSS دوريّاً
- في C++ إدارة إصدارات إعادة توزيع وقت التشغيل و DLL الخارجية
- إدخال مراجعة معلومات الثغرات في التحقّق قبل الإصدار
- تجهيز اختبار دخان حتى لا ينكسر الأمر بتحديث التبعية
هنا «سنفعله مجمَّعاً لاحقاً» هو الأخطر. إن تُرك نصف سنة أو سنة، كبر فرق التحديث أكثر من اللازم، وصارت معالجة الأمان نفسها عملاً ثقيلاً.
flowchart TB
accTitle: عند ترك تحديث التبعيات
accDescr: إن تُرك تحديث التبعيات نصف سنة إلى سنة كبر فرق التحديث أكثر من اللازم، وصارت معالجة الأمان نفسها عملاً ثقيلاً.
pt1["سنفعله مجمَّعاً لاحقاً"] --> ac2["ترك نصف سنة إلى سنة"]
ac2 --> df1["فرق التحديث يكبر أكثر من اللازم"]
df1 --> hw1["المعالجة نفسها تصير عملاً ثقيلاً"]
rg1["المراجعة الدورية"] -.->|"يمكن تجنّب هذا"| hw1
الشكل 18: كلّما تُرك تحديث التبعيات تضخّم الفرق، وصارت المعالجة نفسها عملاً ثقيلاً.
3.11. كيف نتحقّق من كل بند؟
قائمة التحقّق لا تعمل إلا مع طريقة التحقّق. لبنود 3.2 إلى 3.10 نرتّب ما يمكن طرقه فعلاً قبل الإصدار.
| ما نريد التحقّق منه | طريقة التحقّق |
|---|---|
| هل يُطلب رفع؟ | انظر قيمة requestedExecutionLevel في بيان التطبيق. في المصدر app.manifest، وفي مادّة التوزيع Sigcheck من Sysinternals أو محرّر الموارد |
| التوقيع والطابع الزمني | نفّذ في PowerShell Get-AuthenticodeSignature .\app.exe، وانظر هل Status هو Valid وهل دخل TimeStamperCertificate. انظر أيضاً DLL المضمَّنة والمحدِّث واحداً واحداً |
| هل قُتل التحقّق من الشهادات؟ | ابحث في المصدر كلّه عن ServerCertificateValidationCallback و DangerousAcceptAnyServerCertificateValidator و ServerCertificateCustomValidationCallback و CheckCertificateRevocationList |
| كتابة الأسرار مباشرة | ابحث عن Password= و ApiKey و Secret و Token و ConnectionString. لا المصدر الحالي فقط، بل تاريخ المستودع أيضاً |
| تركيب SQL | ابحث عن ربط سلاسل يشمل "SELECT و "INSERT و + ، وتأكّد أنه يمرّ عبر Parameters.Add |
| مصدر تحميل DLL | في Process Monitor ضيّق على العملية المستهدفة، وضَع مرشّحي Path ends with .dll و Result is NAME NOT FOUND. يظهر أين وبأي ترتيب بُحث، فتتحقّق من عدم النظر إلى مجلد غير مقصود |
| ثغرات معروفة في التبعيات | نفّذ dotnet list package --vulnerable --include-transitive |
| هل خرجت أسرار في السجلات؟ | شغّل مرّة ثم ابحث في السجلات الخارجة عن Bearer و Password و Authorization |
بحث الشيفرة يجوز بـ rg (ripgrep) أو بحث Visual Studio. للطرق مجمَّعاً هذا الشكل.
Get-ChildItem -Recurse -Include *.cs,*.vb,*.cpp,*.h,*.config,*.json |
Select-String -Pattern 'ServerCertificateValidationCallback|DangerousAcceptAnyServerCertificateValidator|Password=|ApiKey' |
Select-Object Path, LineNumber, Line
المهم إبقاء التحقّق نفسه في السجلّ. إن أبقيت حتى «بحثنا» و«كانت النتيجة صفر»، اكتفيت في الإصدار التالي بالنظر إلى الفرق فقط.
flowchart TB
accTitle: أثر إبقاء التحقّق في السجلّ
accDescr: تحقّق ما قبل الإصدار حتى إن كانت النتيجة صفراً إن أُبقي في السجلّ اكتفيت في الإصدار التالي بالنظر إلى الفرق فقط.
chk2["تنفيذ التحقّق قبل الإصدار"] --> rec1["تسجيل حتى بحثنا وكانت النتيجة صفراً"]
rec1 --> nx1["الإصدار التالي يكفي فيه النظر إلى الفرق"]
الشكل 19: إبقاء واقعة «تحقّقنا» حتى في السجلّ يكفي بعده بالنظر إلى فرق التحقّق فقط.
4. قائمة التحقّق قبل الإصدار
نجعلها شكلاً يُستخدم كما هو كقالب مراجعة أو حكم شحن. لتسهيل التحقّق بالجدول نرتّب البنود التي تريد النظر إليها في الحدّ الأدنى قبل الإصدار حسب الفئة.
4.1. الصلاحيات وطريقة التشغيل
| بند التحقّق | التحقّق | ملاحظة |
|---|---|---|
التشغيل العادي يعمل بـ asInvoker |
□ | |
| المعالجة التي تحتاج امتيازات مدير مفصولة إلى EXE / خدمة وغيرها | □ | |
| عند استخدام خدمة، حساب التشغيل ليس أقوى من اللازم | □ | |
فصل المسؤوليات بين ما تحت %ProgramFiles% وما تحت بيانات المستخدم |
□ |
4.2. التوزيع والتوقيع
| بند التحقّق | التحقّق | ملاحظة |
|---|---|---|
| توقيع EXE / DLL / MSI / MSIX / المحدِّث | □ | |
| إرفاق طابع زمني للتوقيع | □ | |
| إدخال أجل الشهادة وإجراء التجديد في مسار الإصدار | □ | |
| تحديد طريقة التحقّق من هاش مادّة التوزيع أو كشف العبث | □ |
4.3. التحديث
| بند التحقّق | التحقّق | ملاحظة |
|---|---|---|
| جلب التحديث عبر HTTPS | □ | |
| التحقّق من التوقيع أو الهاش بعد التنزيل | □ | |
| تصميم يصعّب استبدال عنوان مصدر التحديث جزافاً | □ | |
| وجود سياسة تراجع أو إعادة محاولة عند فشل التحديث | □ |
4.4. الأسرار
| بند التحقّق | التحقّق | ملاحظة |
|---|---|---|
| كلمات المرور ومفاتيح API وسلاسل الاتّصال غير مكتوبة مباشرة في الشيفرة | □ | |
| لا سرّ في ملف إعداد نصّ صريح | □ | |
| السرّ الذي يلزم حفظه محليّاً محمي بـ DPAPI / Credential Locker وغيرهما | □ | |
| الإزاح حيث أمكن نحو مصادقة Windows أو بيانات اعتماد المستخدم | □ |
4.5. الاتّصال
| بند التحقّق | التحقّق | ملاحظة |
|---|---|---|
| اتّصال الإنتاج يستخدم HTTPS | □ | |
DangerousAcceptAnyServerCertificateValidator أو => true غير باقٍ في مادّة الشحن |
□ | |
| الوعي بالتحقّق من الإلغاء والتحقّق من اسم المضيف | □ | |
| شيفرة أو إعداد يفترض شهادة تطوير غير مختلط في الإنتاج | □ |
4.6. الإدخال والوصول إلى البيانات
| بند التحقّق | التحقّق | ملاحظة |
|---|---|---|
| SQL معلَّم المعاملات | □ | |
| إدخال سطر الأوامر والملفات و IPC و URI وغيرها له حدّ وفحص صيغة | □ | |
| عمليات المسار مطبَّعة وتمنع الخروج عن الجذر | □ | |
| رسالة الاستثناء لا تُعرَض كما هي على الشاشة | □ |
4.7. DLL وبيئة التشغيل
| بند التحقّق | التحقّق | ملاحظة |
|---|---|---|
| مصدر تحميل DLL مصرَّح به | □ | |
ترتيب البحث مضبوط بـ SetDefaultDllDirectories / AddDllDirectory وغيرهما |
□ | |
| لا تحميل DLL بإسناد إلى الدليل الحالي أو PATH | □ | |
| مجموعة الملفات اللازمة للتحميل الديناميكي في جهة التوزيع معروفة | □ |
4.8. السجلات والتشغيل
| بند التحقّق | التحقّق | ملاحظة |
|---|---|---|
| الرموز وكلمات المرور و PII لا تخرج إلى السجلّ | □ | |
| فصل السجلّ الداخلي عن رسالة المستخدم | □ | |
| مراجعة صلاحيات موضع حفظ dump / trace / log | □ | |
| مراجعة حالة تحديث SDK ومكتبات التبعية | □ |
5. رفض شائع
ما يُشاهَد كثيراً في العمل اليومي تقريباً هذه الاعتقادات.
5.1. «أداة داخلية إذن بخير»
حتّى الأداة الداخلية يوجد فيها بشكل عادي ملف تالف، وسوء تشغيل، وجهاز محمول، ومجلد مشترك، و DLL قديمة، وضبط صلاحيات فضفاض. حتّى بلا نشر على الإنترنت لا يختفي سطح الهجوم.
5.2. «HTTPS إذن آمن»
HTTPS مهمّ، غير أن تعطيل التحقّق من الشهادات يفرّغ المعنى كثيراً. وفي توزيع التحديث يلزم، فضلاً عن HTTPS، التحقّق من أصالة مادّة التوزيع أيضاً.
5.3. «شفّرنا إذن آمن»
إن لم يُرتَّب موضع مفتاح فكّ التشفير وصلاحية الفكّ وحدّ المستخدم وحدّ الجهاز، فالتشفير وحده لا يكفي.
خصوصاً استخدام قيمة محمية بـ LocalMachine بظنّ أنها «سرّ لكل مستخدم» يربك لاحقاً.
5.4. «زيادة السجلات تمكّن التحقيق»
إن كثرت السجلات وحدها وتدفّقت الرموز أو البيانات الشخصية، صار ذلك نفسه حادثاً. إن أردت قابلية التحقيق فاحسم أوّلاً ماذا تُبقي وماذا تخفي.
5.5. «التشغيل كمدير يحلّ الأمر»
أوّلاً مريح، غير أنه لاحقاً يتعسّر غالباً عند UAC والتوزيع والدعم وحدود الصلاحيات وتحميل DLL وموضع حفظ الملفات. أقلّ صلاحية أثبت على المدى الطويل.
flowchart TB
accTitle: مآل «التشغيل كمدير يحلّ الأمر»
accDescr: الاستخدام المعتاد بامتيازات مدير مريح أوّلاً، غير أنه يتعسّر لاحقاً عند UAC والتوزيع والدعم وحدود الصلاحيات وتحميل DLL وموضع حفظ الملفات، وأقلّ صلاحية أثبت على المدى الطويل.
ez1["التشغيل كمدير يحلّ الأمر"] --> ez2["مريح أوّلاً"]
ez2 --> pain1["يتعسّر عند UAC والتوزيع والدعم"]
ez2 --> pain2["يتعسّر عند حدود الصلاحيات وموضع الحفظ"]
lp1["التشغيل بأقلّ صلاحية"] -->|"أثبت على المدى الطويل"| ok2["يمكن تجنّب العسر"]
الشكل 20: راحة الاستخدام المعتاد كمدير أوّلية فقط، وعلى المدى الطويل أقلّ صلاحية أثبت.
6. أولوية تقريبية
إن ثقل فعل الكلّ دفعة واحدة، فالأولوية تقريباً كالتالي.
معيار الترتيب حاصل ضرب حجم الضرر عند وقوع الحادث و انخفاض تكلفة الإصلاح. الفصل 3 مرتَّب بمسار التصميم «صلاحيات ← توزيع ← تنفيذ ← تشغيل»، أمّا هنا فالترتيب «من الأخطر»، لذا لا يتطابق مع ترتيب الفصول. أرفق الفصل المقابل.
- مراجعة امتيازات المدير (3.2)
أوقف أوّلاً الاستخدام المعتاد لـ
requireAdministrator. نطاق الضرر يتغيّر درجة، وغالباً يكفي كتغيير تصميم صغير. - التوقيع والطابع الزمني (3.3) رتّب موثوقية مادّة التوزيع. يكفي إدخاله في الخطوات، وإدخاله لاحقاً يتطلّب إعادة توزيع.
- إخراج الأسرار (3.5) أخرج السرّ من الشيفرة وإعداد النصّ الصريح. الضرر عند التسرّب كبير، وبعد التسرّب لا يُستعاد.
- تصحيح HTTPS + التحقّق من الشهادات (3.6)
احذف عائلة
=> trueمن مادّة الشحن. غالباً يُصلَح بالحذف وحده، وتركه يجعل الاتّصال كلّه غير قابل للثقة. - مراجعة إدخال SQL / الملفات / IPC (3.7) قلّل ربط السلاسل والإدخال بلا تحقّق. العدد كثير، غير أنه يُصلَح موضعاً موضعاً.
- تثبيت تحميل DLL (3.8) أوقف التحميل بالاسم وحده والإسناد إلى PATH. عمل يصلح جزءاً من معالجة الإقلاع، ويفيد أيضاً كوقاية من الأعطال.
- إخفاء السجلات (3.9) حتى لا تصير السجلات كارثة ثانوية عند الحادث. مواضع الخرج كثيرة فيستغرق وقتاً.
- تثبيت تحديث التبعيات كعادة (3.10) اجعله مساراً يُراجَع عند كل إصدار. لا ينتهي بمرّة، فالعمل حتى يصير آلية.
مسار التحديث (3.4) غير داخل في هذا الترتيب لأن بعض التطبيقات لا تحمل تحديثاً تلقائيّاً. إن حملته فانظر إليه بالأولوية نفسها كالرقم 2 التوقيع. مكوّن التحديث أضعف من النواة ومع ذلك في موضع يستطيع إعادة كتابتها.
flowchart TB
accTitle: طريقة وضع الأولوية
accDescr: رُتِّب من الأخطر بحاصل ضرب حجم الضرر عند الحادث وانخفاض تكلفة الإصلاح، وفي تطبيق يحمل تحديثاً تلقائيّاً يُنظَر إلى مسار التحديث بأولوية التوقيع نفسها.
dmg1["حجم الضرر"] --> mul1["حسم الترتيب بالحاصل"]
cst1["انخفاض تكلفة الإصلاح"] --> mul1
mul1 --> ord1["السدّ من الأخطر"]
ord1 -.-> upn1["إن وُجد تحديث تلقائي فمسار التحديث بأولوية التوقيع نفسها"]
الشكل 21: الأولوية تُحسَم بحاصل حجم الضرر وتكلفة الإصلاح، وتُسَدّ الثقوب من الأخطر.
بهذا الترتيب يسهل التقدّم بمعنى «سدّ الثقب الواضح الخطر أوّلاً».
7. الخلاصة
أمان تطوير تطبيقات Windows، حتى قبل إدخال منتج خاص أو آلية ضخمة، يتغيّر كثيراً بترتيب سبع نقاط فقط: الصلاحيات، والتوقيع، والأسرار، والاتّصال، والإدخال، و DLL، والسجلات.
الخطّ الأدنى جملة جملة يصير كالتالي.
- لا تشغّل التطبيق كلّه بامتيازات مدير
- وقّع مادّة التوزيع ومادّة التحديث وأرفق طابعاً زمنيّاً
- لا تضع الأسرار في الشيفرة أو إعداد نصّ صريح
- حتى مع HTTPS لا تقتل التحقّق من الشهادات
- لا تثق بالإدخال الخارجي مثل SQL والملفات و IPC
- لا تترك مصدر تحميل DLL غامضاً
- لا تخرج أسراراً إلى السجلات
- لا تترك مكتبات التبعية بلا متابعة
حديث الأمان واسع، غير أنك لست مضطرّاً لفعل الكلّ من البداية. فقط عدم شحن سلوك افتراضي خطر كما هو كحدّ أدنى يستحق الترتيب في مرحلة مبكّرة جدّاً.
8. روابط مرجعية
- Administrator Broker Model - Win32 apps
- How User Account Control works
- Authenticode Digital Signatures
- Time Stamping Authenticode Signatures
- Sign a Windows app package
- Credential Locker for Windows apps
- CryptProtectData function (dpapi.h)
- CA5359: Do not disable certificate validation
- CA5399: Enable HttpClient certificate revocation list check
- Configuring parameters - ADO.NET Provider for SQL Server
- Connection String Syntax - ADO.NET
- Dynamic-Link Library Security - Win32 apps
- SetDefaultDllDirectories function (libloaderapi.h)
- Data redaction in .NET
- ProtectedData Class - أنّه خاصّ بـ Windows، والتنبيه عند عدم تحميل الـ profile.
- ServicePointManager.ServerCertificateValidationCallback - من .NET 9 فصاعداً يُربَط بإعدادات SocketsHttpHandler.
- dotnet list package command - يمكن التحقّق من الثغرات المعروفة بـ
--vulnerable. - Get-AuthenticodeSignature
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
كيفيّة فصل «المعالجات التي تحتاج امتيازات المسؤول فقط» في تطبيقات Windows
نرتّب هنا تصميماً ملموساً يُبقي واجهة تطبيق Windows عند asInvoker ويفصل المعالجات التي تحتاج امتيازات المسؤول إلى helper EXE، شاملاً UAC ...
حفظ أسرار تطبيقات Windows - تجنّب إعداد النصّ الصريح بـ DPAPI
نرتّب هنا فكرة DPAPI و ProtectedData، والفرق بين CurrentUser و LocalMachine، ونقاط التنفيذ، كي لا تُحفَظ معلومات الاتّصال ورموز API بالنص...
الأنابيب المسمّاة عمليّاً ── IPC القياسيّ في Windows من التصميم إلى الأمان
دليل عمليّ للأنابيب المسمّاة، وسيلة IPC القياسيّة في Windows. يغطّي وضع البايت مقابل وضع الرسالة، وخوادم متعدّدة العملاء، وأمان ACL وانتح...
مدخل إلى ADR (سجلّ قرار العمارة) ── أقلّ وسيلة لتوثيق «لماذا اخترنا هذا التصميم» في التطوير الصغير
لا تخبرنا الشيفرة «لماذا اتُّخذ هذا القرار». نشرح كيفية توثيق أسباب قرارات التصميم عبر ADR (Architecture Decision Record) بصيغة Markdown ...
عندما يُعامَل تطبيق Windows الذي طوّرته شركتك بوصفه فيروساً ── التعامل مع الكشف الخاطئ في Microsoft Defender والتعايش مع أثره على الأداء
نرتّب الإجراء الرسميّ عند كشف Microsoft Defender تطبيق Windows الذي طوّرته شركتك خطأً. نشرح آليّة برامج مكافحة الفيروسات الحديثة، والإبلا...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
لأنّ الأمر يتعلّق بمراجعة تطبيق Windows بأكمله، بما في ذلك تصميم الصلاحيات وطريقة التوزيع وآليّة التحديث وتصميم السجلّات، فهو موضوع يتوافق جيّداً مع تطوير تطبيقات Windows.
الاستشارات التقنية ومراجعة التصميم
إذا أردتم البدء بمراجعة أمان التطبيق القائم وترتيب حدود الصلاحيّات وإعادة تصميم سياسة أداة التحديث، فيمكن ترتيب ذلك ضمن الاستشارة التقنيّة ومراجعة التصميم.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- ما الذي ينبغي النظر إليه أوّلاً في أمان تطبيق Windows؟
- أربع نقاط: عدم طلب امتيازات مدير غير لازمة، وتوقيع الشيفرة، وعدم حفظ الأسرار بنصّ صريح، وعدم تعطيل التحقّق من الشهادات. الأمان الأدنى ليس إضافة ميزة خاصّة بقدر ما هو عدم إبقاء سلوك افتراضي خطر أو تنفيذ فضفاض. تخطّي التحقّق من الشهادات دائماً، وسلاسل الاتّصال بنصّ صريح، وتحميل DLL بإسناد إلى الدليل الحالي، وتنفيذ SQL بربط سلاسل، بنود ينبغي تجنّبها حتى على الخطّ الأدنى.
- هل يُمنَع تشغيل التطبيق كلّه بامتيازات مدير؟
- ينبغي تجنّبه. إن شغّلت التطبيق كلّه بامتيازات مدير، نُفِّذت الأخطاء واستبدال DLL وسوء قراءة ملف الإعداد وعيوب الإدخال الخارجي بالصلاحيات القوية كما هي. تطبيق واجهة المستخدم العادي أساسه asInvoker، وتُفصَل المعالجة التي تحتاج امتيازات مدير إلى عملية أو خدمة منفصلة، ويُجرى الرفع في اللحظة اللازمة فقط. هذا هو الاتجاه الأساسي.
- أين ينبغي حفظ مفاتيح API أو سلاسل الاتّصال؟
- ينبغي أوّلاً الخروج من وضعها بنصّ صريح في الشيفرة المصدرية أو ملفات إعداد مثل appsettings.json. أسرار الحفظ تُوزَّع حسب الاستخدام بين DPAPI / ProtectedData و Credential Locker. وإذا بقيت الرموز أو كلمات المرور أو سلاسل الاتّصال أو البيانات الشخصية في السجلات كما هي، صارت السجلات نفسها بطلة الحادث، لذا يلزم الإخفاء.
- هل يلزم توقيع الشيفرة حتى لتطبيق يُوزَّع داخل المؤسسة؟
- الأفضل اعتباره مقدّمة. في تطبيقات Windows تصير مادّة التوزيع نفسها (EXE / DLL / MSI / MSIX / مكوّن التحديث التلقائي) سطح هجوم. مع توقيع الشيفرة + طابع زمني تحصل على كشف العبث، وثقة المستخدم، وسهولة الشرح التشغيلي. وآلية التحديث أيضاً ينبغي تثبيت مصدر التحديث وجعل العبث قابلاً للكشف عبر HTTPS والتحقّق من التوقيع.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.