安全なクリップボードの処理

OWASP カテゴリ: MASVS-CODE: コード品質

概要

Android には、アプリケーション間でデータをコピーして貼り付けるための強力なフレームワークであるクリップボードが用意されています。この機能を不適切に実装すると、ユーザー関連データが不正な悪意のある行為者やアプリに公開される可能性があります。

クリップボード データの漏洩に関連する具体的なリスクは、アプリの性質と、アプリが処理する個人情報(PII)によって異なります。金融アプリケーションは支払いデータや 2 段階認証(2FA)コードを処理するアプリであるため、影響は特に大きくなります。

クリップボード データを漏洩させるために利用される攻撃ベクトルは、Android のバージョンによって異なります。

  • Android 10 以前のバージョン(API レベル 29)の Android では、バックグラウンド アプリがフォアグラウンド アプリのクリップボード情報にアクセスできるため、悪意のある行為者がコピーされたデータに直接アクセスできる可能性があります。
  • Android 12 以降(API レベル 31)では、アプリがクリップボード内のデータにアクセスして貼り付けるたびに、トースト メッセージがユーザーに表示されるため、攻撃に気付かれにくくなります。また、PII を保護するために、Android は ClipDescription.EXTRA_IS_SENSITIVE または android.content.extra.IS_SENSITIVE の特別なフラグをサポートしています。これにより、デベロッパーはキーボード GUI 内のクリップボード コンテンツのプレビューを視覚的に難読化できるため、コピーされたデータがクリアテキストで視覚的に表示され、悪意のあるアプリによって盗まれる可能性を防ぐことができます。上記のフラグのいずれかを実装しない場合、攻撃者はショルダー サーフィングによって、またはバックグラウンドで実行されている悪意のあるアプリによって、クリップボードにコピーされた機密データを漏洩させることができます。そのようなアプリは、正当なユーザーのアクティビティのスクリーンショットを撮影したり、動画を録画したりします。

影響

クリップボードの不適切な処理を悪用すると、ユーザー関連の機密データや財務データが悪意のある攻撃者によって漏洩する可能性があります。これにより、攻撃者はフィッシング キャンペーンやなりすましなどのさらなる行為を実行できるようになります。

リスクの軽減

センシティブ データを報告する

このソリューションは、キーボード GUI 内のクリップボード コンテンツのプレビューを視覚的に難読化するために使用されます。パスワードやクレジット カードデータなど、コピー可能な機密データには、ClipboardManager.setPrimaryClip() を呼び出す前に ClipDescription.EXTRA_IS_SENSITIVE または android.content.extra.IS_SENSITIVE のフラグを設定する必要があります。

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)以降でのみアプリを実行するようにするには、Android Studio のプロジェクト内の Gradle ビルドファイルでバージョン設定に次の値を設定します。

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 10(API レベル 29)より前の Android バージョンで実行されることを想定している場合、すべてのバックグラウンド アプリがクリップボード データにアクセスできます。このリスクを軽減するには、クリップボードにコピーされたデータを特定の時間が経過した後に消去する関数を実装することをおすすめします。この機能は、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);

リソース