الأمان Security في React Native

من موسوعة حسوب
d security chart

يُهمَل الأمان عادةً عند بناء التطبيقات، إذ يُعَد بناء برمجيات غير قابلة للاختراق تمامًا أمرًا مستحيلًا، إذ لم نخترع بعد قفلًا غير قابل للاختراق (لا تزال خزائن المصارف تتعرض للاختراق). لكن احتمال الوقوع ضحية لهجوم ضار أو التعرض لثغرة أمنية يتناسب عكسيًا مع الجهد الذي تبذله لحماية تطبيقك من أي هجوم. يمكن اختيار قفل مغلاق padlock، لكنه لا يزال صعب التجاوز أكثر من خطاف خزنة.

سنتعرف على أفضل الممارسات لتخزين المعلومات الحساسة والاستيثاق authentication وأمان الشبكة والأدوات التي ستساعدك على تأمين تطبيقك.

تخزين المعلومات الحساسة

لا تخزّن أبدًا مفاتيح واجهة برمجة التطبيقات API الحسّاسة في شيفرة تطبيقك، إذ يمكن لأي شخص يتفقد حزمة التطبيق الوصول إلى أي شيء موجود في هذه الشيفرة. تُعَد أدوات مثل react-native-dotenv و react-native-config أدوات رائعة لإضافة متغيرات خاصة بالبيئة مثل نقاط نهاية واجهة برمجة التطبيقات، ولكن لا ينبغي الخلط بينها وبين متغيرات البيئة من جانب الخادم، والتي يمكن أن تحتوي غالبًا على أسرار ومفاتيح واجهة API.

إن كان لديك مفتاح واجهة API أو سر للوصول إلى بعض الموارد في تطبيقك، فإن الطريقة الأكثر أمانًا للتعامل مع ذلك هي إنشاء طبقة تزامن Orchestration بين تطبيقك والمورد. قد تكون هذه العملية دون خادم (كاستخدام عمليات AWS Lambda أو Google Cloud) والتي يمكنها تمرير الطلب باستخدام سر أو مفتاح واجهة API المطلوب. لا يمكن لمستخدمي واجهة API الوصول إلى الأسرار الموجودة في شيفرة جانب الخادم بالطريقة نفسها التي يمكن للأسرار الموجودة في شيفرة تطبيقك الوصول إليها.

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

البيانات المستمرة Persisted والبيانات غير المستمرة Unpersisted تُكتَب البيانات المستمرة على قرص الجهاز الصلب، مما يتيح لتطبيقك قراءة البيانات عبر عمليات تشغيل التطبيق دون الحاجة إلى إجراء طلب شبكة آخر لجلبها أو مطالبة المستخدم بإعادة إدخالها. ولكن هذا أيضًا يمكن أن يجعل تلك البيانات أكثر عرضة لأن يصل إليها المهاجمون. لا تُكتَب البيانات غير المستمرة على القرص الصلب مطلقًا، لذلك لا توجد بيانات للوصول إليها.

التخزين غير المتزامن Async Storage

التخزين غير المتزامن هو وحدة يحتفظ بها المجتمع لإطار عمل React Native والتي توفر مخزنًا غير متزامن وغير مشفر له زوج مفتاح-قيمة key-value. لا تتشارك التطبيقات بالتخزين غير المتزامن، فلكل تطبيق بيئته المعزولة ولا يمكنه الوصول إلى بيانات التطبيقات الأخرى.

حالات استخدم التخزين غير المتزامن حالات عدم استخدام التخزين غير المتزامن
البيانات المستمرة غير الحساسة عبر تشغيل التطبيق تخزين الرموز Token storage
حالة Redux المستمرة الأسرار Secrets
حالة GraphQL المستمرة
تخزين المتغيرات العامة على مستوى التطبيق

ملاحظات المطور في الويب: التخزين غير المتزامن هو مكافئ React Native للتخزين المحلي من الويب.

التخزين الآمن

لا يتضمّن React Native أي طريقة لتخزين البيانات الحساسة، ولكن هناك حلول قائمة مسبقًا لمنصات Android و iOS.

خدمات Keychain (في iOS)

تتيح لك خدمات Keychain تخزين أجزاء صغيرة من معلومات المستخدم الحساسة بأمان، ويُعَد مكانًا مثاليًا لتخزين الشهادات والرموز وكلمات المرور وأي معلومات حساسة أخرى لا تنتمي إلى التخزين غير المتزامن.

التفضيلات المشتركة الآمنة Secure Shared Preferences (في Android)

التفضيلات المشتركة هي مكافئ مخزن بيانات الأزواج قيمة-مفتاح المستمر في Android. لا تُشفَّر البيانات في التفضيلات المشتركة افتراضيًا، ولكن التفضيلات المشتركة المشفَّرة تغلِّف تفضيلات نظام Android المشتركة، وتشفّر المفاتيح والقيم تلقائيًا.

Keystore في Android

يتيح لك نظام Android Keystore تخزين مفاتيح التشفير في حاوية ليصعب على الجهاز استخراجها.

يمكنك استخدام خدمات iOS Keychain أو Android Secure Shared Preferences إما من خلال كتابة جسرٍ بنفسك أو استخدام مكتبة تغلّفها لك وتوفر واجهة API موحّدة على مسؤوليتك الخاصة. فيما يلي بعض المكتبات التي يمكنك الاطلاع عليها:

  • react-native-sensitive-info الآمنة لنظام iOS، لكنها تستخدم تفضيلات Android المشتركة لنظام Android (وهي غير آمنة افتراضيًا)، ولكن يوجد فرع منها يستخدم Android Keystore.

ضع في حساباتك احتمالية تخزين المعلومات الحساسة أو كشفها عن غير قصد، فقد يحدث ذلك عن طريق الخطأ كحفظ بيانات النموذج الحساسة في حالة الاسترجاع واستمرار شجرة الحالة بأكملها في التخزين غير المتزامن، أو إرسال رموز المستخدم والمعلومات الشخصية إلى خدمة مراقبة التطبيق مثل Sentry أو Crashlytics.

الاستيثاق Authentication والربط العميق Deep Linking

security deep linking

تحتوي تطبيقات الهواتف المحمولة على ثغرة أمنية فريدة غير موجودة على الويب، وهذه الثغرة هي الربط العميق deep linking، والذي هو طريقة لإرسال البيانات مباشرة إلى تطبيق أصيل من مصدر خارجي. يشبه الرابط العميق الشكل app://‎ حيث app هو بروتوكول تطبيقك وأي شيء يتبع الرمز // يمكن استخدامه داخليًا لمعالجة الطلب.

إن أردت بناء تطبيق لمتجر الكتروني على سبيل المثال، فيمكنك استخدام الرابط app://products/1 للربط العميق بتطبيقك وفتح صفحة تفاصيل المنتج لمنتج له المعرّف 1، حيث يشبه ذلك عناوين URL على الويب، ولكن مع اختلاف واحد مهم، وهو أن الروابط العميقة ليست آمنة ولا يجب عليك أبدًا إرسال أي معلومات حساسة فيها.

يرجع سبب عدم أمان الروابط العميقة إلى عدم وجود طريقة مركزية لتسجيل بروتوكولات عناوين URL، ولكن بصفتك مطور تطبيقات، فيمكنك استخدام أي بروتوكول URL تقريبًا تختاره عن طريق إعداده في Xcode على نظام iOS أو إضافة نيّة فعل intent على نظام Android.

لا يوجد ما يمنع أي تطبيق ضار من سرقة رابطك العميق عن طريق التسجيل أيضًا في نفس البروتوكول ثم الحصول على حق الوصول إلى البيانات التي يحتوي عليها رابطك. ليس إرسال شيء كالرابط app://products/1 ضارًا، لكن يُعَد إرسال رموز مصدر قلق أمني.

إذا احتوى نظام التشغيل على تطبيقين أو أكثر للاختيار من بينها عند فتح رابط، فسيعرض نظام Android للمستخدم نافذة modal ويطلب منه اختيار التطبيق الذي سيستخدمه لفتح الرابط. لكن يختار نظام التشغيل iOS عنك في هذه الحالة، لذلك لن يكون المستخدم على دراية بذلك. اتخذت شركة Apple خطوات لمعالجة هذه المشكلة في إصدارات iOS الأحدث (iOS 11) حيث أسست مبدأ من يأتي أولًا يُخدَّم أولًا، على الرغم من أنه لا يزال استغلال هذه الثغرة الأمنية ممكنًا بطرق مختلفة. سيسمح استخدام الروابط العامة universal links بالربط بالمحتوى داخل تطبيقك في نظام iOS بأمان.

بروتوكول OAuth2 وعمليات إعادة التوجيه Redirects

يحظى بروتوكول الاستيثاق OAuth2 بشعبية كبيرة في الوقت الحاضر، ويفتخر بأنه البروتوكول الأكثر اكتمالاً وأمانًا، ويعتمد بروتوكول OpenID Connect أيضًا عليه. يُطلَب من المستخدم في بروتوكول OAuth2 الاستيثاق عبر طرف ثالث، وإن نجح الاستيثاق، فسيعيد هذا الطرف الثالث التوجيه إلى التطبيق الطالب برمز تحقّق يمكن استبداله برمز JSON Web Token أو اختصارًا JWT، والذي هو معيار مفتوح لنقل المعلومات الآمن بين الأطراف على الويب.

تُعَد خطوة إعادة التوجيه هذه آمنة على الويب، لأن عناوين URL على الويب مضمونة أنها فريدة. لكن هذا ليس صحيحًا بالنسبة للتطبيقات، لأنه لا توجد طريقة مركزية لتسجيل مخططات عناوين URL، لذلك يجب إضافة فحص إضافي على شكل PKCE من أجل معالجة هذا القلق الأمني.

يرمز PKCE -الذي يُنطق "Pixy"- إلى Proof of Key Code Exchange إثبات تبادل شيفرة المفتاح، وهو توسّع لمواصفات بروتوكول OAuth 2. يتضمن PKCE إضافة طبقة أمان إضافية تتحقق من أن طلبات الاستيثاق وتبادل الرموز آتية من نفس العميل. يستخدم PKCE خوارزمية التشفير المختصَرة SHA 256، حيث تنشئ خوارزمية SHA 256 "توقيعًا" فريدًا لنص أو ملف من أي حجم، ولكنه هذا التوقيع:

  • دائمًا له نفس الطول بغض النظر عن ملف الإدخال.
  • مضمون لإعطاء نفس النتيجة دائمًا لنفس الدخل.
  • باتجاه واحد (أي لا يمكنك عكس إعداده للكشف عن الدخل الأصلي).

لديك الآن قيمتان:

  • code_verifier: سلسلة عشوائية كبيرة ينشئها العميل.
  • code_challenge: قيمة SHA 256 الخاصة بالسلسلة code_verifier.

يرسل العميل code_challenge أثناء طلب ‎/authorize الأولي إلى code_verifier التي يحتفظ بها في الذاكرة. يرسل العميل أيضًا code_verifier التي اُستخدِمت لإنشاء code_challenge بعد إعادة طلب الترخيص authorize بصورة صحيحة. سيحسب IDP بعد ذلك قيمة code_challenge ويتأكد من تطابقها مع القيمة التي أُعِدت في طلب ‎/authorize الأول، ويرسل رمز الوصول إن كانت القيم متطابقة فقط.

هذا يضمن أن التطبيق الذي أطلق تدفق الترخيص الأولي فقط سيكون قادرًا على تبادل رمز التحقق لرمز JWT بنجاح. لذلك حتى إن تمكّن أحد التطبيقات الضارة من الوصول إلى رمز التحقق، فسيكون عديم الفائدة (اطّلع على هذا المثال لمزيد من المعلومات).

المكتبة التي يجب مراعاتها في بروتوكول OAuth الأصيل هي react-native-app-auth، والتي هي عبارة عن SDK للتواصل مع مزوّدي بروتوكول OAuth2، وتغلّف مكتبات AppAuth-iOS و AppAuth-Android الأصيلة ويمكنها دعم PKCE.

ملاحظة: يمكن للمكتبة React-native-app-auth أن تدعم PKCE فقط إذا كان مزوّد الهويات Identity Provider الخاص بك يدعمها.

diagram pkce

أمن الشبكة

يجب أن تستخدم واجهات API الخاصة بك دائمًا تشفير SSL. يحمي تشفير SSL من قراءة البيانات المطلوبة كنص صرف بين وقت مغادرتها الخادم وقبل وصولها إلى العميل، حيث يمكنك معرفة أن نقطة النهاية آمنة، لأنها تبدأ بـ https://‎ عوضًا عن http://‎.

تثبيت SSL

قد يؤدي استخدام نقاط نهاية https إلى ترك بياناتك عرضة للاعتراض. لن يثق العميل بالخادم باستخدام https إلا إذا كان بإمكانه تقديم شهادة صالحة موقَّعة من هيئة شهادات موثوقة ومثبَّتة مسبقًا على العميل. يمكن للمهاجم الاستفادة من ذلك عن طريق تثبيت شهادة CA جذرية ضارة على جهاز المستخدم، لذلك سيثق العميل في جميع الشهادات التي وقعها المهاجم. وبالتالي فإن الاعتماد على الشهادات وحدها قد يجعلك عرضة لهجوم الوسيط man-in-the-middle.

تثبيت SSL أو SSL pinning هو تقنية يمكن استخدامها من جانب العميل لتجنب هذا الهجوم، وتعمل عن طريق تضمين (أو تثبيت) قائمة الشهادات الموثوقة للعميل أثناء التطوير، بحيث تُقبَل الطلبات الموقَّعة بإحدى الشهادات الموثوقة فقط، ولن تُقبَل أي شهادات موقعة ذاتيًا.

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

الخلاصة

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

مصادر