การจัดการคลิปบอร์ดที่ปลอดภัย

หมวดหมู่ OWASP: MASVS-CODE: คุณภาพโค้ด

ภาพรวม

Android มีเฟรมเวิร์กอันทรงประสิทธิภาพที่เรียกว่าคลิปบอร์ดสําหรับการคัดลอกและวางข้อมูลระหว่างแอปพลิเคชัน การใช้งานฟีเจอร์นี้อย่างไม่เหมาะสมอาจทำให้ข้อมูลที่เกี่ยวข้องกับผู้ใช้ถูกเปิดเผยต่อผู้ไม่ประสงค์ดีหรือแอปพลิเคชันที่ไม่ได้รับอนุญาต

ความเสี่ยงที่เฉพาะเจาะจงซึ่งเชื่อมโยงกับการเปิดเผยข้อมูลในคลิปบอร์ดจะขึ้นอยู่กับลักษณะของแอปพลิเคชันและข้อมูลส่วนบุคคลที่ระบุตัวบุคคลนั้นได้ (PII) ที่แอปพลิเคชันจัดการ แอปพลิเคชันทางการเงินจะได้รับผลกระทบอย่างมาก เนื่องจากอาจเปิดเผยข้อมูลการชำระเงิน หรือแอปที่จัดการรหัสการตรวจสอบสิทธิ์แบบ 2 ปัจจัย (2FA)

เวกเตอร์การโจมตีที่อาจใช้ประโยชน์เพื่อลักลอบนำข้อมูลในคลิปบอร์ดออกจะแตกต่างกันไปตามเวอร์ชัน Android ดังนี้

  • Android เวอร์ชันเก่ากว่า Android 10 (API ระดับ 29) อนุญาตให้แอปพลิเคชันที่ทำงานอยู่เบื้องหลังเข้าถึงข้อมูลคลิปบอร์ดของแอปที่ทำงานอยู่เบื้องหน้า ซึ่งอาจทำให้ผู้ไม่ประสงค์ดีเข้าถึงข้อมูลที่คัดลอกไว้ได้โดยตรง
  • ตั้งแต่ Android 12 เป็นต้นไป (API ระดับ 31) ทุกครั้งที่แอปพลิเคชันเข้าถึงข้อมูลในคลิปบอร์ดและวางข้อมูลนั้น ระบบจะแสดงข้อความแจ้งให้ผู้ใช้ทราบ ซึ่งทำให้การโจมตีถูกสังเกตได้ยากขึ้น นอกจากนี้ Android ยังรองรับ Flag พิเศษ ClipDescription.EXTRA_IS_SENSITIVE หรือ android.content.extra.IS_SENSITIVE เพื่อปกป้อง PII ด้วย ซึ่งช่วยให้นักพัฒนาแอปสร้างความสับสนให้กับตัวอย่างเนื้อหาในคลิปบอร์ดภายใน GUI ของแป้นพิมพ์ได้ เพื่อป้องกันไม่ให้แอปพลิเคชันที่เป็นอันตรายแสดงข้อมูลที่คัดลอกในรูปแบบข้อความธรรมดาและอาจขโมยข้อมูลได้ การไม่ใช้ Flag ใด Flag หนึ่งข้างต้นอาจทำให้ผู้โจมตีสามารถลักลอบนำข้อมูลที่ละเอียดอ่อนซึ่งคัดลอกไปยังคลิปบอร์ดไปใช้ได้ด้วยการแอบดูจากด้านหลังหรือผ่านแอปพลิเคชันที่เป็นอันตรายซึ่งถ่ายภาพหน้าจอหรือบันทึกวิดีโอกิจกรรมของผู้ใช้ที่ถูกต้องขณะทำงานอยู่เบื้องหลัง

ผลกระทบ

การใช้การจัดการคลิปบอร์ดที่ไม่เหมาะสมอาจส่งผลให้ผู้ไม่ประสงค์ดีขโมยข้อมูลทางการเงินหรือข้อมูลที่ละเอียดอ่อนที่เกี่ยวข้องกับผู้ใช้ ซึ่งอาจช่วยให้ผู้โจมตีดำเนินการต่อได้ เช่น แคมเปญฟิชชิงหรือการโจรกรรมข้อมูลประจำตัว

การลดปัญหา

แจ้งข้อมูลที่ละเอียดอ่อน

โซลูชันนี้ใช้เพื่อสร้างความสับสนให้กับการแสดงตัวอย่างเนื้อหาในคลิปบอร์ดภายใน GUI ของแป้นพิมพ์ ข้อมูลที่ละเอียดอ่อนซึ่งคัดลอกได้ เช่น รหัสผ่านหรือข้อมูลบัตรเครดิต ควรแจ้งว่าไม่เหมาะสมด้วย 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 (API 29) จะป้องกันไม่ให้กระบวนการทำงานเบื้องหลังเข้าถึงข้อมูลคลิปบอร์ดในแอปพลิเคชันเบื้องหน้า

หากต้องการบังคับให้แอปทำงานใน Android 10 (API 29) ขึ้นไปเท่านั้น ให้ตั้งค่าต่อไปนี้สำหรับการตั้งค่าเวอร์ชันในไฟล์บิลด์ 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);

แหล่งข้อมูล