안전한 클립보드 처리

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

개요

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

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

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

  • Android 10 (API 수준 29) 이전 버전에서는 백그라운드 애플리케이션이 포그라운드 앱 클립보드 정보에 액세스할 수 있으므로 악의적인 행위자가 복사된 데이터에 직접 액세스할 수 있습니다.
  • Android 12 (API 수준 31)부터는 애플리케이션이 클립보드 내의 데이터에 액세스하여 붙여넣을 때마다 사용자에게 토스트 메시지가 표시되므로 공격이 눈에 띄지 않게 진행되기가 더 어려워집니다. 또한 PII를 보호하기 위해 Android는 ClipDescription.EXTRA_IS_SENSITIVE 또는 android.content.extra.IS_SENSITIVE 특수 플래그를 지원합니다. 이를 통해 개발자는 키보드 GUI 내에서 클립보드 콘텐츠 미리보기를 시각적으로 난독화하여 복사된 데이터가 일반 텍스트로 시각적으로 표시되지 않도록 하고 악의적인 애플리케이션에 의해 도용될 가능성을 줄일 수 있습니다. 앞서 언급한 플래그 중 하나를 구현하지 않으면 공격자가 어깨너머로 엿보거나 백그라운드에서 실행되는 동안 합법적인 사용자의 활동을 스크린샷하거나 동영상을 녹화하는 악의적인 애플리케이션을 통해 클립보드에 복사된 민감한 정보를 유출할 수 있습니다.

영향

잘못된 클립보드 처리를 악용하면 악의적인 행위자가 사용자 관련 민감한 정보 또는 금융 데이터를 유출할 수 있습니다. 이는 공격자가 피싱 캠페인 또는 신원 도용과 같은 추가 작업을 수행하는 데 도움이 될 수 있습니다.

완화 조치

민감한 정보 플래그 지정

이 솔루션은 키보드 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 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);

리소스