أساسيّات أمان ميزة التحديث التلقائيّ - الأنماط السيّئة وأفضل الممارسات

· · تطوير Windows, الأمان, updater, التحديث التلقائيّ, التوقيع, MSIX, ClickOnce

1. الخلاصة أوّلاً

بأسلوب فضفاض ولكنّه عمليّ في الاستخدام، يمكن قول التالي.

  • إن كانت المتطلّبات تنسجم، فضّل أوّلاً البنى الجاهزة للتحديث مثل MSIX App Installer أو ClickOnce
  • إن احتجت إلى updater خاصّ، فإنّ أوّل ما ينبغي إدخاله ليس الـ UI، بل التحقّق من التوقيع والتعافي عند الفشل
  • معلومات التحديث مثل latest.json يجب التعامل معها بوصفها signed metadata، لا ملفّ إعداد غير موقّع
  • TLS ضروريّ لكنّه ليس شرطاً كافياً
  • اجعل قرار التحديث ليس «لأنّ الـ server يقول ذلك» بل «لأنّ الـ client تحقّق وقرّر أنّه صحيح»
  • افصل مفاتيح التوقيع بين التطوير والإنتاج، واحمها بـ HSM أو خدمة توقيع
  • عند فشل التحديث، اجعل السلوك fail-closed لا fail-open
  • updater لا يملك مواجهة rollback من المفترض أن يُعاد به إلى نسخة هشّة، فالأكثر أماناً افتراض ذلك مسبقاً
  • إن كنت في مرحلة لا يمكن فيها بعدُ إدخال التحقّق من التوقيع، فإنّ توزيع installer موقّع يدويّاً أكثر أماناً من التحديث التلقائيّ

باختصار، جوهر التحديث التلقائيّ ليس «كيف تنزّل»، بل «بمن تثق، وأين تتحقّق، وكيف تعود إن انكسر».

2. لماذا التحديث التلقائيّ منطقة خطرة

الميزات العاديّة تبقى مغلقة داخل التطبيق. في المقابل، يمتلك الـ updater الأمور الثلاثة التالية دفعةً واحدة.

  1. يذهب لجلب ملفّات من الخارج
  2. يثق بهذه الملفّات
  3. يستبدل المنفّذات الموجودة

أي أنّ مساراً لتنفيذ كود اعتباطيّ مدمج أصلاً داخل المنتج منذ البداية.

من الالتباسات الشائعة هنا قول «HTTPS، إذن آمن». بالتأكيد TLS ضروريّ. لكن ما يحميه أساساً هو مسار الاتّصال وصحّة الجهة المتّصل بها. أمّا أن يُخترق سيرفر التحديث ذاته، أو يُوضع artifact خاطئ على CDN رسميّ، أو يُستبدل manifest غير موقّع، فهذه أمور لا يكفي TLS وحده فيها.

في الواقع، حتّى التهديدات التي رتّبها TUF فقط تتضمّن للأنظمة المعنيّة بالتحديث ما يلي.

  • جعل المستخدم يثبّت برمجيّات خبيثة اعتباطيّة
  • rollback يجبر العودة إلى نسخة قديمة هشّة
  • freeze يمنع رؤية النسخة الجديدة
  • mix-and-match يخلط بين metadata وartifacts غير متّسقة فيما بينها

أي أنّ التحديث التلقائيّ ليس «نقل ملفّات» بل «توزيع ثقة». وحين تُصمَّم هذه النقطة، يبدأ التحديث التلقائيّ بالعمل بأمان.

3. الأنماط السيّئة

أوّلاً، نلخّص الأشكال الخطرة التي تُرى كثيراً في الواقع العمليّ.

النمط السيّئ ما الخطر فيه الحدّ الأدنى من الإصلاح
جلب version.json عبر HTTPS وتشغيل zip / exe من الـ URL مباشرةً ضعيف أمام اختراق origin، واستبدال الإعدادات، وسوء التوزيع تحويله إلى التحقّق من signed metadata وartifact في جهة الـ client
توقيع البايناري فقط، وترك الـ manifest غير موقّع إمكانيّة العبث بـ URL وversion وchannel وعَلَم التحديث الإلزاميّ جعله signed manifest يتضمّن version / hash / size / channel / expiry
وضع مفاتيح التوقيع في ملفّات على PC التطوير أو CI في حال الاختراق، يمكن توزيع برمجيّات خبيثة بتوقيع رسميّ HSM / خدمة توقيع + مسار موافقات + سجلّ تدقيق
«تجاهل خطأ التحقّق ومتابعة العمل» عند فشل التحديث يفتح أضعف مسار حين تقع الحادثة فعلاً اجعله fail-closed
تحديث بالكتابة فوق دون الإبقاء على النسخة القديمة انقطاع كهرباء، نقص قرص، أو فشل في المنتصف يجعل التشغيل مستحيلاً staging + activation ذرّيّ + rollback
السماح بنُسخ قديمة بمجرّد المقارنة بين الإصدارات يمرّ rollback إلى نسخة هشّة release version متزايد رتيباً وحفظ أعلى نسخة معروفة
تشغيل الـ updater كاملاً بصلاحيّات مدير يتّسع نطاق الضرر عند الاختراق اجعل التنزيل والتحقّق بصلاحيّات منخفضة، وافصل عمليّة الاستبدال فقط في helper بأقلّ صلاحيّات
البدء بالتحديث التفاضليّ تعقيد التنفيذ يزيد فجوات التحقّق ابدأ أوّلاً بتحديث حزمة كاملة

فيما يلي نظرة بشيء من التفصيل.

3.1 التوقّف عند «HTTPS، إذن لا بأس»

هذا هو الأكثر شيوعاً.

  • قراءة latest.json عند الإقلاع
  • استخراج downloadUrl
  • تنزيل الـ zip / exe
  • فكّ الضغط واستبدال الملفّات
  • الإنهاء

من حيث الشكل يبدو الأمر معقولاً، لكنّ جذر الثقة يميل أكثر من اللازم نحو ردّ الـ server. إذا اختُرق سيرفر التحديث أو إعدادات التوزيع، فيمكن توزيع تحديث خبيث فوق HTTPS صحيح.

TLS ضروريّ. لكنّ TLS وحده لا يُكمل تصميم الـ updater.

3.2 التوقيع موجود، لكنّ الـ client لا يتحقّق

حتّى لو وقّعت الملفّات عند الإصدار، إن لم يتحقّق منها الـ client فلا فائدة من التوقيع.

الشكل الشائع:

  • التوقيع موجود في الـ CI
  • لكنّ الـ updater يفحص الـ hash فقط
  • وذلك الـ hash ذاته يأتي من manifest غير موقّع

في هذه الحالة، حين يُستبدل الـ manifest يُستبدل الـ hash معه. «أنا أفحص الـ hash، إذن آمن» لا يصحّ إلّا إذا حُمي مصدر الـ hash نفسه أيضاً.

3.3 الـ manifest غير موقّع

ما ينبغي حمايته فعلاً في أنظمة التحديث ليس الملفّ التنفيذيّ وحده. المعلومات التالية على الأقلّ خطِرٌ العبثُ بها.

  • version / release id
  • الـ URL واسم الملفّ المراد تنزيله
  • hash / size
  • channel (stable / beta وغيرها)
  • هل هو تحديث إلزاميّ
  • نظام التشغيل / المعماريّة المنطبقَين
  • صلاحيّة الـ metadata
  • أدنى version مطلوب للـ updater

أي أنّ من المناسب التعامل بحسّ: ضع كلّ المعلومات المستخدمة في قرار التحديث ضمن signed metadata.

3.4 إدارة مفاتيح التوقيع غير مرتّبة

أمان ميزة التحديث يعود بنسبة كبيرة إلى أمان إدارة المفاتيح.

إن كانت مفاتيح توقيع الإنتاج موضوعة على النحو التالي، فالخطر كبير.

  • متروكة في certificate store على PC التطوير
  • مرفوعة كـ secret في CI على هيئة .pfx
  • يجري توزيع المفتاح الخاصّ نفسه محلّيّاً على عدّة أشخاص
  • توقيع التطوير وتوقيع الإنتاج في سلسلة الثقة نفسها

في هذه الحالة، حتّى لو كان الـ updater صحيحاً، لا يمكن إيقاف «تحديث خبيث موقّع رسميّاً».

3.5 تحديث بالكتابة فوق دون الإبقاء على النسخة القديمة

في التحديث، تصميم حالة الفشل أهمّ من تصميم حالة النجاح.

  • انقطاع التنزيل في المنتصف
  • فشل فكّ الضغط
  • انقطاع التيّار في منتصف الاستبدال
  • إقلاع النسخة الجديدة لكنّ الـ migration الأوّليّ يفشل

في هذه اللحظة، إن كانت النسخة القديمة قد حُذفت، يصعب التعافي. في الواقع العمليّ، «التطبيق توقّف عن العمل عند العميل» مشكلة أكبر من «فشل التحديث».

3.6 عدم التفكير في rollback

حتّى النسخ الرسميّة الموقّعة قد تكون مناسبة للمهاجم إن كانت قديمة وهشّة.

مثلاً:

  • في version 1.8 ثغرة معروفة
  • العميل وصل إلى 2.3
  • يعيد المهاجم توزيع 1.8

إن مرّ هذا، فالتوقيع نفسه صحيح لكنّه خطر.

لا يكفي «هل هذا موقّع»، بل يجب رؤية «هل يجوز تثبيت هذه النسخة الآن».

3.7 fail-open

هذا أسوأ ما يجب فعله في الإنتاج.

  • عند فشل التحقّق من التوقيع، إظهار تحذير فقط ومتابعة العمل
  • وجود hidden flag يسمح بتجاهل خطأ انتهاء صلاحيّة الشهادة
  • بقاء skipVerify=true للتصحيح في الإنتاج أيضاً

في أوقات الأعطال أو الهجمات، تصبح هذه الثغرات هي المسار الرئيسيّ.

4. أفضل الممارسات

4.1 ابدأ بالاعتماد على بنى التحديث الجاهزة

من الأكثر أماناً أن تشكّك أوّلاً في حقيقة الحاجة إلى updater خاصّ.

على Windows، طالما أنّ المتطلّبات تنسجم، يسهل تفضيل التالي.

  • MSIX + App Installer
  • ClickOnce
  • Store / MDM / بنى التوزيع الداخليّة
  • MSI + إدارة توزيع من جانب المؤسّسة

السبب بسيط: يمكن إلقاء جزء من مسؤوليّة التحديث ذاتها على الـ platform. بالتأكيد تقلّ الحرّيّة، لكنّ توافق UI التحديث وmanifest التوزيع وتوقيع الحزمة والتشغيل يصبح أيسر.

تنشأ الحاجة إلى updater خاصّ مثلاً في الحالات التالية.

  • الرغبة في التحكّم الصارم بقنوات متعدّدة stable / beta / preview
  • الرغبة في توزيع تدريجيّ ومعدّل rollout
  • الرغبة في التحكّم الدقيق بتوقيت التحديث لأسباب عمل خاصّة
  • وجود تكوين لا يتلاءم مع MSIX / ClickOnce

حتّى في هذه الحالة، الفهم الأكثر استقراراً ليس «نريد حرّيّة» بل «نتحمّل مسؤوليّة التحديث بأنفسنا».

4.2 ضع نقطة انطلاق الثقة في جهة الـ client

الـ updater الآمن لا يصدّق ردّ الـ server كما هو. يحتاج جانب الـ client على الأقلّ إلى الأمرين التاليَين.

  1. مفتاح عامّ موثوق أو سلسلة شهادات
  2. آليّة للتحقّق من metadata الموقّع بهذا المفتاح

باختصار، يجب إيجاد حالة لا يكون فيها الحكم «الـ server يقول إنّ هذه أحدث نسخة»، بل «تستطيع جهة الـ client تأكيد أنّ هذا الـ metadata صادر عن موقّع موثوق ويعدّه أحدث نسخة».

4.3 صمّم حول signed metadata بوصفه المحور

كحدّ أدنى، ضع البنود التالية ضمن metadata التحديث واجعلها هدف التوقيع.

البند سبب إدراجه
release version / release id منع rollback، التدقيق
اسم الـ artifact، URL، package type تثبيت الملفّ المستهدف
hash، size كشف العبث، كشف التوزيع التالف
channel عدم خلط beta داخل stable
OS / architecture المستهدف منع التوزيع الخاطئ
minimum updater version إيقاف الـ updater القديم عند تغيير البروتوكول
expires_at مواجهة freeze
published_at التدقيق، التشخيص
mandatory / optional منع العبث بفروع UX التحديث

المهمّ هنا، تجميع كلّ مادّة قرار التحديث في signed metadata. الميل إلى وضع المنطق على الـ client وحماية صحّة المعلومات بالتوقيع يقلّل من الحوادث.

4.4 تحقّق من الـ artifact ذاته أيضاً

بعد التحقّق من الـ metadata، تحقّق أيضاً ممّا يلي في الـ artifact المنزَّل.

  • size
  • hash
  • توقيع الحزمة / code signing
  • جهة الإصدار والمعرّف المتوقَّع

عند التعامل مع PE / MSI / MSIX على Windows، الأكثر أماناً افتراض إجراء التحقّق من Authenticode أو توقيع الحزمة في جانب الـ client. أمّا على macOS، فالتمسّك بمتطلّب Developer ID وnotarization حتّى في مسار التحديث يجعل الأمر أكثر استقراراً.

4.5 احمِ المفاتيح بالتشغيل لا بالميزة

تظهر الفروق في إدارة المفاتيح في التشغيل أكثر منها في التنفيذ.

كحدّ أدنى، يفضّل الفصل التالي.

  • مفتاح توقيع التطوير
  • مفتاح توقيع الـ staging
  • مفتاح توقيع الإنتاج

ثمّ، بالنسبة لمفاتيح الإنتاج، يستحسن تصميم ما يشمل:

  • HSM
  • خدمة توقيع سحابيّة
  • signing system مع مسار موافقات
  • سجلّ تدقيق
  • إجراء key rotation
  • توقيع مع timestamp

تشمله جميعاً.

«إذا نجحت بناءات الإنتاج، يوقّع الـ CI تلقائيّاً» مريح، لكنّ نصف قطر الضرر عند الاختراق يكبر أيضاً. ينبغي على الأقلّ أن يكون بالإمكان تتبّع من وقّع ماذا ومتى.

عند نضوج التشغيل، فصل root trust الذي نادراً ما يتغيّر عن مفتاح metadata التحديث الذي يُعاد توقيعه باستمرار يزيد الأمان أكثر. تصميم يبقي root قريباً من offline ويستخدم مفتاحاً منفصلاً لـ metadata التحديث يجعل خفض نصف قطر الضرر عند تسرّب المفتاح أيسر.

4.6 fail-closed وstaged update

تدفّق التحديث الأساسيّ بالترتيب التالي.

  1. جلب الـ metadata
  2. التحقّق من التوقيع وانتهاء الصلاحيّة وversion
  3. تنزيل الـ artifact إلى منطقة staging
  4. التحقّق من hash / size / التوقيع
  5. تجهيز activation مع الإبقاء على النسخة القديمة
  6. التبديل عند إعادة التشغيل أو عبر helper مخصّص
  7. التحقّق من سلامة الإقلاع الأوّل
  8. rollback عند وجود مشكلة

المهمّ هنا أمران: لا تستبدل قبل انتهاء التحقّق لا تتقدّم إن فشلت.

4.7 قلّص صلاحيّات الـ updater

من المرغوب تجنّب تشغيل الـ updater كاملاً بصلاحيّات مدير.

الفصل المثاليّ:

  • التنزيل والتحقّق: صلاحيّات منخفضة
  • استبدال الملفّات الفعليّ فقط: helper بأقلّ صلاحيّات
  • لا يقوم الـ helper بأكثر من «وضع package متحقَّق منه في المكان المحدّد»

كلّما احتاج التصميم إلى رفع الصلاحيّات، يصبح الخطر أكبر إذا لم يُفصَل بوضوح ما الذي تحقّقت منه قبل الرفع.

4.8 سدّ rollback / freeze / mix-and-match منذ البداية

إضافة هذه الأمور لاحقاً مؤلمة، فمن الأفضل إدخالها أوّلاً.

  • مواجهة rollback
    يحتفظ الـ client بـ «أعلى metadata version / release version رآها حتّى الآن»، ويرفض ما هو أقدم منها

  • مواجهة freeze
    جعل الـ metadata يحمل expiry ورفض metadata قديم جدّاً

  • مواجهة mix-and-match
    ضمان الاتّساق بين الـ metadata. على الأقلّ، ثبّت داخل الـ manifest نفسه hash / size / version للـ artifact المستهدف

إضافةً إلى ذلك، إمكانيّة توزيع blocklist يرفض build معيّناً أو minimum allowed version عبر signed metadata يسرّع احتواء الحوادث.

حتّى من دون اعتماد TUF كما هو، هذه الخصائص الثلاث مهمّة جدّاً.

4.9 ابدأ من التحديث الكامل أوّلاً

التحديث التفاضليّ مفيد لعرض الحزمة، لكنّه معقّد كتنفيذ أوّليّ.

  • من أيّ نسخة قديمة إلى أيّ نسخة جديدة يطبَّق التفاضل
  • hash المُسبق قبل تطبيق التفاضل
  • hash النهائيّ بعد تطبيق التفاضل
  • التعافي عند الفشل في المنتصف
  • التطبيق الجزئيّ وتنظيف التفاضلات القديمة

هذه الأمور تتزايد دفعةً واحدة. في الإصدار الأوّليّ، يكفي استبدال حزمة كاملة موقّعة بأمان.

5. الحدّ الأدنى من التكوين الآمن

حتّى وإن لم نذهب إلى TUF كامل، فإنّ الحدّ الأدنى من التكوين الآمن لـ updater خاصّ يكون عادةً كالتالي.

5.1 ما يحمله الـ client

  • مفتاح root العامّ الموثوق، أو سلسلة شهادات مثبَّتة
  • الـ version الجاري تشغيله حاليّاً
  • أعلى metadata version / release version شُوهد سابقاً
  • channel المسموح به
  • النسخة السابقة المخصّصة لـ rollback

5.2 ما يردّه الـ server

  • update metadata موقّع
  • artifact موقّع، أو موقّع من قبل platform
  • عند الحاجة، معلومات blocklist / minimum allowed version

5.3 التدفّق النموذجيّ

metadata を取る
  ↓
署名・expiry・version・channel を検証
  ↓
成果物を staging へ落とす
  ↓
size / hash / package signature を検証
  ↓
旧版を残したまま activation
  ↓
初回起動に失敗したら rollback

المهمّ هنا، لا شيء يكتمل بمجرّد ردّ سيرفر التحديث. ما يجعله يكتمل هو trust anchor الذي يحمله الـ client ومنطق التحقّق.

6. كيف نفكّر في مشاريع Windows

في تطبيقات Windows، الترتيب الأسهل هو الانطلاق عكسيّاً من طريقة التوزيع.

  • إن كانت المتطلّبات تنسجم، فاستخدم MSIX App Installer
  • إن كان تطبيق .NET داخليّ بنطاق per-user مناسب، فـ ClickOnce
  • إن كنت تحتاج إلى service أو driver أو shell extension أو تحكّم بقناة خاصّة، فقارن أيضاً MSI + updater خاصّ

إلّا أنّ اختيار updater خاصّ لا يقلّل ما ينبغي فعله. بل يزيده.

  • التحقّق من Authenticode / توقيع الحزمة
  • signed manifest
  • مواجهة rollback
  • فصل صلاحيّات helper التحديث
  • استراتيجيّة تحديث الـ updater ذاته

الشكل الخطر الشائع على Windows هو خطّ مستقيم على هيئة DownloadFile -> unzip -> kill process -> overwrite -> restart. قد يعمل، لكنّه ضعيف من ناحيتَي الأمان والقدرة على التعافي معاً.

التشغيل الذي يعتمد على دفع المستخدم لتجاوز تحذيرات SmartScreen أو UAC عبر «معلومات إضافيّة → تشغيل» ليس تصميماً للتحديث، بل تطبيع للتحذيرات. لإنشاء مسار تحديث صحيح، لا تجعل المستخدم يعتاد التحذيرات، بل اقترب من تكوين توزيع وتحقّق لا يُنتج تحذيرات بسهولة.

نلخّص مقارنة طرق التوزيع نفسها أيضاً في المقال التالي.
كيف نختار طريقة توزيع تطبيقات Windows - جدول قرار MSI / MSIX / ClickOnce / xcopy / updater خاصّ

7. الحدّ الأدنى من قائمة المراجعة

قبل إخراج updater خاصّ، تحقّق على الأقلّ ممّا يلي.

  • metadata التحديث موقّع
  • الـ metadata يحتوي على version / hash / size / channel / expiry
  • جانب الـ client يتحقّق من التوقيع وversion
  • يجري التحقّق من hash الـ artifact وتوقيع platform
  • مفتاح توقيع الإنتاج مفصول عن بيئة التطوير
  • يبقى سجلّ استخدام المفاتيح ووثائق الموافقة
  • يجري استخدام توقيع مع timestamp
  • في تحديث الـ staging، يجري التبديل مع الإبقاء على النسخة القديمة
  • هناك شروط وإجراءات لـ rollback
  • عند فشل التحقّق، يتوقّف بـ fail-closed
  • هناك سياسة لتحديث الـ updater ذاته
  • يمكن توزيع blocklist / minimum allowed version
  • هناك kill switch لإيقاف التوزيع التدريجيّ
  • يمكن مراقبة معدّل الفشل ومعدّل rollback وفشل التحقّق من التوقيع

إن كانت هذه القائمة تحوي فجوات كثيرة، فإنّ إحكام نموذج ثقة التوزيع أوّلاً أكثر فاعليّة من بناء UI الـ updater.

8. الخلاصة

يمكن تكثيف أمان ميزة التحديث التلقائيّ في الجملة التالية تقريباً.

صمّم لا «راحة التحديث»،
بل «بمن نثق، وكيف يتحقّق الـ client من تلك الثقة».

ثمّ، بصياغة فضفاضة للقرار العمليّ:

  • إن كانت البنى الجاهزة تكفي، فابدأ بالاعتماد عليها
  • إن كنت ستبني updater خاصّاً، فأدخل signed metadata وإدارة المفاتيح قبل HTTPS
  • updater لا يصمَّم له تعافٍ من الفشل وrollback يكون مؤلماً في الإنتاج
  • الـ updater ليس ميزة توزيع، بل هو الحدّ الأمنيّ للمنتج ذاته

إن كان تكوينك الحاليّ قريباً من latest.json + zip swap، فإنّ أوّل ما ينبغي إصلاحه ليس معالجة التنزيل بل طريقة وضع الثقة. مجرّد إصلاح هذه النقطة يغيّر مستوى الخطر تغييراً كبيراً.

9. مراجع

مواضيع ذات صلة

هذه صفحة موضوع قريبة من هذا الموضوع. انطلاقاً من المقال، يمكنك الانتقال إلى الخدمات أو المقالات الأخرى ذات الصلة.

مواضيع تقنيّة عن Windows

نقطة الدخول التي تجمع المواضيع التقنيّة عن تطوير Windows والتحقيق في الأعطال والاستفادة من الأصول الموجودة.

الخدمات التي يتّصل بها هذا الموضوع

تطوير تطبيقات Windows

التحديث التلقائيّ ليس مجرّد UI، بل تصميم يشمل طريقة التوزيع والصلاحيّات والتعافي والتشغيل. يمكننا التعامل بدءاً من ترتيب طريقة التحديث في تطوير تطبيقات Windows الجديدة أو مراجعة البرمجيّات الموجودة.

الاستشارة التقنيّة ومراجعة التصميم

يمكن الاستشارة بدءاً من مرحلة الترتيب لأسئلة مثل «هل نحتاج إلى updater خاصّ؟» و«هل MSIX / ClickOnce كافٍ؟» و«أين الخطر في تصميم التحديث الحاليّ؟».

ملف المؤلّف

小村 豪

ممثّل شركة كومورا سوفت ذ.م.م.

يتمحور حول تطوير برمجيّات Windows والاستشارة التقنيّة والتحقيق في الأعطال، مع نقاط قوّة في المشاريع التي تبقى فيها أصول قديمة وفي تحقيقات الأعطال التي يصعب رؤية أسبابها.

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

روابط عامة

العودة إلى المدونة