안전한 클립보드 처리

OWASP 카테고리: MASVS-CODE: 코드 품질

개요

Android는 애플리케이션 간에 데이터를 복사하여 붙여넣기 위한 강력한 프레임워크인 클립보드를 제공합니다. 이 기능을 부적절하게 구현하면 사용자 관련 데이터가 승인되지 않은 악의적인 행위자 또는 애플리케이션에 노출될 수 있습니다.

클립보드 데이터 노출과 관련된 구체적인 위험은 애플리케이션의 특성과 처리하는 개인 식별 정보 (PII)에 따라 다릅니다. 금융 애플리케이션의 경우 결제 데이터 또는 2단계 인증 (2FA) 코드를 처리하는 앱이 노출될 수 있으므로 특히 영향이 큽니다.

클립보드 데이터를 유출하는 데 활용할 수 있는 공격 벡터는 Android 버전에 따라 다릅니다.

  • Android 10 (API 수준 29)보다 오래된 Android 버전에서는 백그라운드 애플리케이션이 포그라운드 앱 클립보드 정보에 액세스할 수 있으므로 악의적인 행위자가 복사된 데이터에 직접 액세스할 수 있습니다.
  • Android 12 이상 (API 수준 31)부터 애플리케이션이 클립보드 내 데이터에 액세스하여 붙여넣을 때마다 토스트 메시지가 사용자에게 표시되므로 공격을 눈치채지 못하는 일이 줄어듭니다. 또한 Android는 PII를 보호하기 위해 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)
    }
}

자바

// 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 스튜디오의 프로젝트 내 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);

리소스