التعامل مع الحافظة بأمان

فئة OWASP: MASVS-CODE: جودة الرمز

نظرة عامة

يوفّر Android إطار عمل فعّالاً يُعرف باسم الحافظة لـ نسخ البيانات ولصقها بين التطبيقات. ويمكن أن يؤدي التنفيذ غير السليم لهذه الميزة إلى تعريض البيانات المرتبطة بالمستخدمين لجهات ضارة أو تطبيقات غير مصرّح لها.

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

تختلف متجهات الهجوم التي يمكن الاستفادة منها لاستخراج بيانات الحافظة حسب إصدار Android:

  • تسمح إصدارات Android الأقدم من Android 10 (مستوى واجهة برمجة التطبيقات 29) للتطبيقات التي تعمل في الخلفية بالوصول إلى معلومات الحافظة في التطبيق الذي يعمل في المقدّمة، ما قد يسمح للجهات المسيئة بالوصول مباشرةً إلى أي بيانات تم نسخها.
  • بدءًا من Android 12 (مستوى واجهة برمجة التطبيقات 31)، في كل مرة يصل فيها تطبيق إلى البيانات داخل الحافظة ويلصقها، تظهر رسالة مؤقتة للمستخدم، ما يجعل من الصعب عدم ملاحظة الهجمات. بالإضافة إلى ذلك، لحماية معلومات تكشف الهوية الشخصية، يتيح Android استخدام العلامة الخاصة ClipDescription.EXTRA_IS_SENSITIVE أو android.content.extra.IS_SENSITIVE. يسمح ذلك للمطوّرين بإخفاء معاينة محتوى الحافظة بشكل مرئي ضمن واجهة المستخدم الرسومية للوحة المفاتيح، ما يمنع عرض البيانات المنسوخة بشكل مرئي بنص واضح، ويمنع التطبيقات الضارة من سرقتها. قد يؤدي عدم تنفيذ إحدى العلامات المذكورة أعلاه إلى السماح للمهاجمين باستخراج البيانات الحسّاسة المنسوخة إلى الحافظة إما من خلال التجسس على الشاشة أو من خلال تطبيقات ضارة، التي تأخذ لقطات شاشة أو تسجّل فيديوهات لأنشطة المستخدم الشرعي أثناء تشغيلها في الخلفية.

التأثير

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

الإجراءات المخفّفة

وضع علامة على البيانات الحسّاسة

يتم استخدام هذا الحل لإخفاء معاينة محتوى الحافظة بشكل مرئي ضمن واجهة المستخدم الرسومية للوحة المفاتيح. يجب وضع علامة ClipDescription.EXTRA_IS_SENSITIVE أو android.content.extra.IS_SENSITIVE على أي بيانات حسّاسة يمكن نسخها، مثل كلمات المرور أو بيانات بطاقات الائتمان، قبل استدعاء ClipboardManager.setPrimaryClip().

Kotlin

// If your app is compiled with the API level 33 SDK or higher.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
    }
}

// If your app is compiled with API level 32 SDK or lower.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean("android.content.extra.IS_SENSITIVE", true)
    }
}

Java

// If your app is compiled with the API level 33 SDK or higher.
PersistableBundle extras = new PersistableBundle();
extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
clipData.getDescription().setExtras(extras);

// If your app is compiled with API level 32 SDK or lower.
PersistableBundle extras = new PersistableBundle();
extras.putBoolean("android.content.extra.IS_SENSITIVE", true);
clipData.getDescription().setExtras(extras);

فرض استخدام أحدث إصدارات Android

يمنع فرض تشغيل التطبيق على إصدارات Android الأحدث من Android 10 (المستوى 29 من واجهة برمجة التطبيقات) أو التي تساويه، العمليات التي تعمل في الخلفية من الوصول إلى بيانات الحافظة في التطبيق الذي يعمل في المقدّمة.

لفرض تشغيل التطبيق على Android 10 (المستوى 29 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث فقط، اضبط القيم التالية لإعدادات الإصدار في ملفات تصميم Gradle ضمن مشروعك في "استوديو Android".

أنيق

android {
      namespace 'com.example.testapp'
      compileSdk [SDK_LATEST_VERSION]

      defaultConfig {
          applicationId "com.example.testapp"
          minSdk 29
          targetSdk [SDK_LATEST_VERSION]
          versionCode 1
          versionName "1.0"
          ...
      }
      ...
    }
    ...

Kotlin

android {
      namespace = "com.example.testapp"
      compileSdk = [SDK_LATEST_VERSION]

      defaultConfig {
          applicationId = "com.example.testapp"
          minSdk = 29
          targetSdk = [SDK_LATEST_VERSION]
          versionCode = 1
          versionName = "1.0"
          ...
      }
      ...
    }
    ...

حذف محتوى الحافظة بعد فترة زمنية محدّدة

إذا كان من المفترض تشغيل التطبيق على إصدارات Android الأقدم من Android 10 (مستوى واجهة برمجة التطبيقات 29)، يمكن لأي تطبيق يعمل في الخلفية الوصول إلى بيانات الحافظة. للحدّ من هذا الخطر، من المفيد تنفيذ دالة تمحو أي بيانات تم نسخها إلى الحافظة بعد فترة زمنية محدّدة. يتم تنفيذ هذه الدالة تلقائيًا بدءًا من Android 13 (مستوى واجهة برمجة التطبيقات 33). بالنسبة إلى إصدارات Android الأقدم، يمكن إجراء عملية الحذف هذه من خلال تضمين المقتطف التالي في رمز التطبيق.

Kotlin

//The Executor makes this task Asynchronous so that the UI continues being responsive
backgroundExecutor.schedule({
    //Creates a clip object with the content of the Clipboard
    val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    val clip = clipboard.primaryClip
    //If SDK version is higher or equal to 28, it deletes Clipboard data with clearPrimaryClip()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        clipboard.clearPrimaryClip()
    } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
    //If SDK version is lower than 28, it will replace Clipboard content with an empty value
        val newEmptyClip = ClipData.newPlainText("EmptyClipContent", "")
        clipboard.setPrimaryClip(newEmptyClip)
     }
//The delay after which the Clipboard is cleared, measured in seconds
}, 5, TimeUnit.SECONDS)

Java

//The Executor makes this task Asynchronous so that the UI continues being responsive

ScheduledExecutorService backgroundExecutor = Executors.newSingleThreadScheduledExecutor();

backgroundExecutor.schedule(new Runnable() {
    @Override
    public void run() {
        //Creates a clip object with the content of the Clipboard
        ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData clip = clipboard.getPrimaryClip();
        //If SDK version is higher or equal to 28, it deletes Clipboard data with clearPrimaryClip()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            clipboard.clearPrimaryClip();
            //If SDK version is lower than 28, it will replace Clipboard content with an empty value
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
            ClipData newEmptyClip = ClipData.newPlainText("EmptyClipContent", "");
            clipboard.setPrimaryClip(newEmptyClip);
        }
    //The delay after which the Clipboard is cleared, measured in seconds
    }, 5, TimeUnit.SECONDS);

الموارد