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

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

نظرة عامة

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

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

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

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

التأثير

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

إجراءات التخفيف

الإبلاغ عن البيانات الحسّاسة

يتم استخدام هذا الحلّ لإخفاء معاينة محتوى الحافظة visually ضمن واجهة المستخدم الرسومية للوحة المفاتيح. يجب وضع علامة عليها باستخدام 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 Studio.

رائع

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);

المراجع