טיפול מאובטח בלוח הלוחות

קטגוריה ב-OWASP: MASVS-CODE: איכות הקוד

סקירה כללית

מערכת Android מציעה מסגרת חזקה שנקראת לוח להעתקה ולהדבקה של נתונים בין אפליקציות. הטמעה לא נכונה של התכונה הזו עלולה לחשוף נתונים שקשורים למשתמשים לגורמים זדוניים או לאפליקציות לא מורשות.

הסיכון הספציפי שמשויך לחשיפת נתוני הלוח תלוי בטבע האפליקציה ובפרטים האישיים המזהים (PII) שהיא מטפלת בהם. ההשפעה גבוהה במיוחד על אפליקציות פיננסיות, כי הן עלולות לחשוף נתוני תשלומים, או אפליקציות שמטפלות בקודים של אימות דו-שלבי (2FA).

וקטור ההתקפה שאפשר לנצל כדי לחלץ נתונים מהלוח משתנה בהתאם לגרסה של Android:

  • בגרסאות Android ישנות יותר מ-Android 10 (רמת API 29), לאפליקציות ברקע יש גישה למידע שבמרכזית של אפליקציות בחזית, וכך גורמים זדוניים עלולים לקבל גישה ישירה לכל הנתונים שהועתקו.
  • החל מגרסה 12 של Android ואילך (רמת API‏ 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 בגרסה 10 ואילך (API 29) או בגרסה זהה מונעת מתהליכים ברקע לגשת לנתוני הלוח באפליקציה שבחזית.

כדי לאכוף שהאפליקציה תפעל רק ב-Android 10 (API 29) ואילך, צריך להגדיר את הערכים הבאים להגדרות הגרסה בקובצי ה-build של Gradle בפרויקט ב-Android Studio.

Groovy

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 (רמת API‏ 29), כל אפליקציה שפועלת ברקע יכולה לגשת לנתוני הלוח. כדי לצמצם את הסיכון הזה, מומלץ להטמיע פונקציה שמוחקת את כל הנתונים שהועתקו ללוח אחרי פרק זמן מסוים. הפונקציה הזו מתבצעת באופן אוטומטי החל מ-Android 13 (רמת API ‏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);

משאבים