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);