Como manusear a área de transferência com segurança

Categoria do OWASP: MASVS-CODE - Qualidade do código (link em inglês)

Visão geral

O Android oferece um framework poderoso chamado área de transferência para copiar e colar dados entre aplicativos. Uma implementação inadequada desse recurso pode expor dados relacionados ao usuário a agentes ou aplicativos maliciosos não autorizados.

O risco específico associado à exposição de dados da área de transferência depende da natureza do aplicativo e das informações de identificação pessoal (PII) que ele processa. O impacto é especialmente alto para aplicativos financeiros, já que eles podem expor dados de pagamento ou apps que processam códigos de autenticação de dois fatores (2FA).

Os vetores de ataque que podem ser aproveitados para extrair dados da área de transferência variam de acordo com a versão do Android:

  • As versões do Android anteriores ao Android 10 (nível 29 da API) permitem que apps em segundo plano acessem informações da área de transferência de apps em primeiro plano, o que pode permitir o acesso direto a dados copiados por agentes maliciosos.
  • A partir do Android 12 (nível 31 da API), sempre que um app acessa e cola dados na área de transferência, uma mensagem de aviso é mostrada ao usuário, dificultando que ataques passem despercebidos. Além disso, para proteger PII, o Android oferece suporte à flag especial ClipDescription.EXTRA_IS_SENSITIVE ou android.content.extra.IS_SENSITIVE. Isso permite que os desenvolvedores ofusquem visualmente a visualização do conteúdo da área de transferência na GUI do teclado, impedindo que os dados copiados sejam mostrados visualmente em texto não criptografado e potencialmente roubados por aplicativos maliciosos. Não implementar uma das flags mencionadas acima pode permitir que invasores extraiam dados sensíveis copiados para o área de transferência por shoulder surfing ou por aplicativos maliciosos que, enquanto são executados em segundo plano, fazem capturas de tela ou gravam vídeos de atividades legítimas do usuário.

Impacto

A exploração de um manuseio inadequado da área de transferência de dados pode resultar na exfiltração de dados sensíveis ou financeiros relacionados ao usuário por agentes maliciosos. Isso pode ajudar os invasores a realizar outras ações, como campanhas de phishing ou roubo de identidade.

Mitigações

Sinalizar dados sensíveis

Essa solução é usada para ofuscar visualmente a visualização do conteúdo da área de transferência na GUI do teclado. Todos os dados sensíveis que podem ser copiados, como senhas ou dados de cartão de crédito, precisam ser sinalizados com ClipDescription.EXTRA_IS_SENSITIVE ou android.content.extra.IS_SENSITIVE antes de chamar 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);

Aplicar as versões mais recentes do Android

A aplicação do app para versões mais recentes ou iguais ao Android 10 (API 29) impede que os processos em segundo plano acessem dados da área de transferência no aplicativo em primeiro plano.

Para forçar o app a ser executado apenas no Android 10 (API 29) ou versões mais recentes, defina os seguintes valores para as configurações de versão nos arquivos de build do Gradle no projeto no 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"
          ...
      }
      ...
    }
    ...

Excluir o conteúdo da área de transferência após um período definido

Se o aplicativo for destinado a versões anteriores ao Android 10 (nível 29 da API), qualquer app em segundo plano poderá acessar os dados do Clipboard. Para reduzir esse risco, é útil implementar uma função que limpe todos os dados copiados para a área de transferência após um período específico. Essa função é executada automaticamente a partir do Android 13 (nível 33 da API). Para versões mais antigas do Android, essa exclusão pode ser realizada incluindo o snippet abaixo no código do aplicativo.

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

Recursos