يحمي نظام التشغيل Android المستخدمين من التطبيقات الضارة ويوفّر تجربة واجهة مستخدم موثوقة. يشمل إطار عمل "أمان النشاط" القواعد وقيود المنصة. تمنع هذه القواعد والقيود حدوث انقطاعات غير مرغوب فيها في واجهة المستخدم، واختطاف المهام، وغير ذلك من التهديدات الأمنية. تتعلّق هذه التهديدات بوقت وكيفية ظهور مكوّنات التطبيق على الشاشة. ويقيّد أحد المكوّنات الرئيسية لإطار العمل هذا بدء الأنشطة من الخلفية.
قيود على بدء الأنشطة في الخلفية
يحدث تشغيل النشاط في الخلفية (BAL) عندما يحاول تطبيق ليس في المقدّمة، أو لا يتضمّن أنشطة مرئية، أو تلقّى PendingIntent من تطبيق آخر، بدء نشاط جديد. هذا هو إطلاق نشاط في الخلفية (BAL).
على الرغم من وجود حالات استخدام مشروعة، مثل بدء تشغيل تطبيق ساعة منبّه، تؤدي عمليات BAL غير المقيدة إلى تجربة مستخدم سيئة وتتسبّب في ثغرات أمنية.
لماذا يتم حظرها؟
منذ الإصدار 10 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 29)، فرضت المنصة قيودًا على الحالات التي يمكن فيها للتطبيقات بدء الأنشطة من الخلفية. تساعد إجراءات الحماية هذه في منع السلوكيات الضارة للتطبيقات وتحسين تجربة المستخدم من خلال الحدّ من إساءة الاستخدام الشائعة، بما في ذلك:
- اختطاف واجهة المستخدم والإعلانات المنبثقة: يطلق تطبيق يعمل في الخلفية نشاطًا بشكل غير متوقّع (غالبًا ما يكون إعلانًا) فوق التطبيق الذي يتفاعل معه المستخدم حاليًا، ما يؤدي إلى اختطاف جلسته.
- التصيّد الاحتيالي وانتحال الهوية: يطلق تطبيق يعمل في الخلفية نشاطًا ينتحل هوية تطبيق آخر (على سبيل المثال، شاشة تسجيل دخول مزيفة لتطبيق مشروع) من أجل سرقة بيانات اعتماد المستخدم. ويتم ذلك غالبًا من خلال هجوم "شطيرة النشاط"، حيث يتم إدراج نشاط ضار في حزمة المهام الخاصة بتطبيق مشروع.
- الاستيلاء على النقرات: يعرض تطبيق يعمل في الخلفية نشاطًا شفافًا أو محجوبًا فوق تطبيق آخر لاعتراض نقرات المستخدم وخداعه لاتّخاذ إجراءات غير مقصودة.
- تنشيط التطبيق: هو مكوّن يعمل في الخلفية من أحد التطبيقات ويوقظ مكوّنات تطبيق آخر تعمل في المقدّمة بهدف تعزيز مقاييس المستخدمين النشطين يوميًا بشكل غير مشروع.
الخدمات التي تعمل في المقدّمة (للمهام المستمرة)
إذا كان تطبيقك يحتاج إلى تنفيذ مهمة طويلة الأمد في الخلفية يجب أن يكون المستخدم على دراية بها، مثل تشغيل الموسيقى أو تتبُّع تمرين رياضي، عليك استخدام خدمة تعمل في المقدّمة. يجب أن تعرض الخدمة التي تعمل في المقدّمة إشعارًا دائمًا لا يمكن للمستخدم إغلاقه. يمكن أن يوفّر هذا الإشعار عناصر تحكّم تفاعلية (مثل أزرار التشغيل/الإيقاف المؤقت لتطبيق موسيقى). يساعد ذلك في إبقاء المستخدم على اطّلاع على المعلومات والتحكّم فيها، ولكن بدون مقاطعة نشاطه من خلال عرض شاشة كاملة.
من خلال اتّباع هذا التسلسل الهرمي، بدءًا بالإشعارات العادية والانتقال إلى الخيارات الأكثر تدخلاً عند الضرورة فقط، يمكنك تقديم تجربة أفضل وأكثر قابلية للتوقّع للمستخدمين.
حالات السماح ببدء الأنشطة في الخلفية (استثناءات)
يمكن لأحد التطبيقات بدء نشاط من الخلفية إذا تم استيفاء أحد الشروط التالية:
- يحتوي التطبيق على نافذة مرئية، مثل نشاط في المقدّمة.
- التطبيق هو محرر أسلوب الإدخال (IME) الحالي.
- يتم بدء النشاط من خلال
PendingIntentأرسله النظام (على سبيل المثال، من خلال النقر على إشعار). - يملك التطبيق الإذن
SYSTEM_ALERT_WINDOWالذي منحه المستخدم. - تم منح التطبيق الإذن "
START_ACTIVITIES_FROM_BACKGROUND". - يكون التطبيق مرتبطًا بخدمة تم منحها إذنًا ببدء الأنشطة في الخلفية.
- يتم بدء التشغيل من خلال تطبيق مشغّل التطبيقات على الجهاز، مثلاً عندما ينقر المستخدم على رمز تطبيق أو يتفاعل مع أداة.
- يتم التشغيل من جزء أساسي في نظام التشغيل يجب أن يعمل في جميع الأوقات، مثل خدمة "الهاتف" التي تبدأ شاشة المكالمة الواردة.
إجراءات أمان جديدة وموافقات مطلوبة
لتعزيز الأمان بشكل أكبر، قدّم نظام التشغيل Android قواعد أكثر صرامة تتطلّب الموافقة الصريحة على التطبيقات التي تستخدم PendingIntent وIntentSender لتشغيل الأنشطة. لا يُسمح بتشغيل التطبيق إلا إذا وافق التطبيق الذي أنشأ
PendingIntent أو التطبيق الذي أرسله على منح امتيازات التشغيل في الخلفية.
في معظم الحالات، يجب أن يكون التطبيق الذي يرسل PendingIntent هو التطبيق الذي يوافق على مشاركة البيانات، لأنّه عادةً ما يكون التطبيق الذي يتفاعل معه المستخدم مباشرةً (على سبيل المثال، النقر على زر).
يجب أن يوافق المرسلون على استخدام PendingIntent
عندما يستهدف تطبيقك الإصدار 14 من نظام التشغيل Android (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، لن يمنح امتيازات BAL تلقائيًا عند إرسال PendingIntent. إذا لم توافق بشكل صريح، قد يتم حظر تشغيل النشاط، ما لم يمنح صانع PendingIntent امتيازاته الخاصة.
لضمان نجاح عملية الإطلاق، على المرسِل الموافقة على منح امتيازاته من خلال استدعاء ActivityOptions.setPendingIntentBackgroundActivityStartMode()، والوضع المقترَح هو ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE (تمت إضافته في حزمة تطوير البرامج (SDK) الإصدار 36).
هذا الوضع أكثر صرامة وأمانًا. يتم منح الإذن فقط إذا كان تطبيق الإرسال
ظاهرًا على الشاشة في لحظة إرسال PendingIntent. يضمن ذلك بشكل أكبر أنّ بدء النشاط هو نتيجة مباشرة لتفاعل المستخدم مع تطبيقك.
استخدِم ActivityOptions.setPendingIntentBackgroundActivityStartMode() لمنح امتيازات.
// Sender Side
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
try {
myPendingIntent.send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "The PendingIntent was canceled", e);
}
// Sender Side
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE
}
try {
myPendingIntent.send(options.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.e(TAG, "The PendingIntent was canceled", e)
}
يجب أن يوافق صنّاع المحتوى على استخدام PendingIntent
عندما يستهدف تطبيقك الإصدار 15 من نظام التشغيل Android (المستوى 35 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، لن يمنح تطبيق ينشئ PendingIntent أذونات التشغيل في الخلفية تلقائيًا. للسماح للمرسِل باستخدام امتيازات BAL في تطبيقك، عليك الموافقة على ذلك بشكل صريح.
استخدِم ActivityOptions.setPendingIntentCreatorBackgroundActivityStartMode() لمنح الامتيازات.
// Creator Side
Intent intent = new Intent(context, MyActivity.class);
ActivityOptions options = ActivityOptions.makeBasic().setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
PendingIntent pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE, options.toBundle());
// Creator Side
val intent = Intent(context, MyActivity::class.java)
val options = ActivityOptions.makeBasic().apply {
pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
val pendingIntent = PendingIntent.getActivity(context, REQUEST_CODE, intent,
PendingIntent.FLAG_IMMUTABLE, options.toBundle())
إطلاق التطبيق باستخدام IntentSender
تنطبق قيود BAL نفسها أيضًا عند تشغيل الأنشطة باستخدام
IntentSender. بما أنّه يتم الحصول على IntentSender من خلال
PendingIntent.getIntentSender، فإنّه يخضع لمتطلبات الموافقة المشابهة.
- بدءًا من الإصدار 14 من نظام التشغيل Android (المستوى 34 لواجهة برمجة التطبيقات)، يتطلّب استخدام Context.startIntentSender() موافقة من جانب المرسِل. يجب أيضًا تقديم حزمة
ActivityOptionsهنا.
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
flagsValues, extraFlags, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
context.startIntentSender(myIntentSender, fillInIntent, flagsMask,
flagsValues, extraFlags, options.toBundle())
- اعتبارًا من Android 17 (المستوى 37 لواجهة برمجة التطبيقات أو الإصدارات الأحدث)، يتطلّب استخدام IntentSender.sendIntent() موافقة من جانب المرسِل.
ActivityOptions options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
myIntentSender.sendIntent(context, code, intent, onFinished, handler,
requiredPermission, options.toBundle());
val options = ActivityOptions.makeBasic().apply {
pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
}
myIntentSender.sendIntent(context, code, intent, onFinished, handler,
requiredPermission, options.toBundle())
- استخدام ActivityResultLauncher<IntentSenderRequest>: تستخدم واجهة برمجة التطبيقات AndroidX هذه Context.startIntentSender() داخليًا، وبالتالي تتأثر بقيود BAL.
مخطط التسلسل: قيود BAL
يوضّح هذا المخطّط البياني عملية تشغيل نشاط بأمان باستخدام PendingIntent. يعتمد نجاح عملية الإطلاق على سلسلة امتيازات صالحة، حيث يمنح تطبيق واحد على الأقل من التطبيقات المشارِكة امتيازاته ولديه القدرة على إطلاق نشاط من الخلفية.
- إنشاء التفويض (التطبيق "أ" - صانع المحتوى)
- إنشاء تطبيق Creator
PendingIntent - إذا كان التطبيق يستهدف الإصدار 35 من حزمة تطوير البرامج (SDK) أو الإصدارات الأحدث، على صانع المحتوى تفويض امتيازات BAL بشكل صريح باستخدام setPendingIntentCreatorBackgroundActivityStartMode() إذا كان يريد استخدام امتيازاته. لا يتم تفويض أي امتيازات بشكل تلقائي.
- يتم بعد ذلك تسليم
PendingIntentإلى تطبيق آخر (التطبيق "ب")
- إنشاء تطبيق Creator
- الإطلاق والمساهمة (التطبيق B - المرسِل)
- في وقت لاحق، يبدأ تطبيق "المُرسِل" عملية التشغيل من خلال طلب
PendingIntent.send(). - إذا كان التطبيق يستهدف الإصدار 34 من حزمة تطوير البرامج (SDK) أو الإصدارات الأحدث، يجب أن يساهم المُرسِل بشكل صريح بامتيازاته الخاصة باستخدام setPendingIntentBackgroundActivityStartMode() إذا كان يريد استخدام امتيازاته. لا يتم تفويض أي امتيازات بشكل تلقائي.
- في وقت لاحق، يبدأ تطبيق "المُرسِل" عملية التشغيل من خلال طلب
- التحقّق من صحة أمان نظام التشغيل Android
- يعترض "نظام التشغيل Android" طلب التشغيل ويجري عملية تحقّق من الأمان.
- وتقيّم شرطَين:
- هل فوّض تطبيق "صانع المحتوى" امتيازاته، وهل يستوفي حاليًا أحد استثناءات BAL العامة؟
- هل ساهم تطبيق المُرسِل بامتيازاته؟ وهل يستوفي تطبيق المُرسِل حاليًا أحد استثناءات BAL العامة؟
- النتيجة
- مسموح به: إذا تم استيفاء شرط واحد على الأقل من الشرطَين في الخطوة 3، سيتم التحقّق من صحة سلسلة الأذونات. يبدأ نظام التشغيل Android النشاط المستهدف، ويتلقّى المرسِل نتيجة ناجحة.
- محظور: إذا لم يتم استيفاء أي من الشرطين، سيحظر النظام عملية الإطلاق. لا يتلقّى تطبيق المُرسِل قيمة إرجاع مباشرة أو استثناء يشير إلى حدوث خطأ. بدلاً من ذلك، يسجّل نظام التشغيل Android داخليًا الرسالة "تم حظر بدء نشاط في الخلفية" في Logcat، والتي يجب أن يراجعها المطوّرون لتحديد المشاكل وحلّها.
منع اختراق المهام
لمنع هجمات اختطاف المهام (مثل "Activity Sandwich")، يقدّم الإصدار 15 من نظام التشغيل Android قواعد جديدة للتطبيقات التي تستهدف المستوى 37 لواجهة برمجة التطبيقات أو الإصدارات الأحدث.
- القاعدة 1: ضمن مهمة واحدة، لا يمكن بدء نشاط إلا من خلال نشاط آخر ينتمي إلى التطبيق نفسه (أي له معرّف UID نفسه) مثل النشاط الحالي في أعلى المهمة.
- القاعدة 2: لا يُسمح إلا بنشاط ضمن مهمة في المقدّمة تتطابق مع معرّف المستخدم (UID) الخاص بالنشاط الأعلى ترتيبًا بإنشاء مهمة جديدة أو نقل مهمة أخرى حالية إلى المقدّمة.
موافقة المطوّر على ميزات الحماية أثناء المهام
يمكن تفعيل هذا السلوك بدءًا من الإصدار 37 من حزمة SDK المستهدَفة، ويجب الموافقة صراحةً على تفعيله. تم تصميم هذه الميزة لمنع اختطاف المهام (أو تداخل الأنشطة)، حيث يمكن لتطبيق ضار تشغيل نشاط في مهمة تطبيقك لانتحال هويته وسرقة بيانات المستخدم.
تفعيل ميزات الحماية
للموافقة على تفعيل ميزة "إحصاءات أمان التطبيق" في تطبيقك، اضبط السمة android:allowCrossUidActivitySwitchFromBelow على القيمة false في ملف AndroidManifest.xml. هذا إعداد على مستوى التطبيق يحمي جميع الأنشطة في تطبيقك تلقائيًا.
إنشاء استثناءات لأنشطة معيّنة
إذا فعّلت هذا الخيار لتطبيقك ولكنك بحاجة إلى السماح بتشغيل نشاط محدّد وموثوق به من خلال تطبيقات أخرى، يمكنك إنشاء استثناء مستهدَف. لاستثناء نشاط واحد من هذه الحماية، استدعِ الدالة setAllowCrossUidActivitySwitchFromBelow(true) ضمن الدالة onCreate() الخاصة بهذا النشاط. ويتيح ذلك إطلاق نشاط واحد بينما تظل بقية تطبيقك محمية.
تحديد المشاكل وحلّها
فلترة Logcat للعثور على الرسائل ذات الصلة باستخدام تعبير عادي يتم استخدام العلامة
ActivityTaskManager بشكل متكرر، ويمكن أن تساعد الفلترة حسب
ActivityTaskManager في عزل السجلات.
فهم رسائل السجلّ الرئيسية
بدء محظور (خطأ): تشير هذه الرسالة إلى أنّه تم حظر بدء نشاط.
- المعنى: تم رفض بدء نشاط لأنّ خيار الموافقة اللازم PendingIntent غير متوفّر في المرسِل (الذي يستهدف المستوى 34 من حزمة تطوير البرامج (SDK) أو مستوى أعلى) أو المنشئ (الذي يستهدف المستوى 35 من حزمة تطوير البرامج (SDK) أو مستوى أعلى).
- الإجراء: عليك تعديل الرمز البرمجي ليشمل خيار المشاركة الصحيح في ActivityOptions.
عند تحليل السجلّات، تحقَّق من الحقول التالية:
- realCallingPackage: التطبيق الذي أرسل PendingIntent. هذا هو المُرسِل.
- callingPackage: التطبيق الذي أنشأ PendingIntent. هذا هو صانع المحتوى.
وضع التدقيق الصارم
بدءًا من Android 16، يمكن لمطوّر تطبيقات تفعيل "وضع التدقيق الصارم" لتلقّي إشعار عند حظر تشغيل نشاط معيّن (أو عند احتمال حظره عند رفع الحد الأدنى لإصدار حزمة تطوير البرامج (SDK) المستهدَف للتطبيق).
في ما يلي نموذج للرمز البرمجي الذي يتيح التفعيل من بداية تطبيقك أو نشاطك أو أي مكوّن آخر من مكونات تطبيقك في طريقة Application.onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectBlockedBackgroundActivityLaunch()
.penaltyLog()
.build());
)
}