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

Categoria do OWASP: MASVS-CODE - Qualidade do código

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, na sigla em inglês) 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 usados para exfiltrar 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 aplicativos em segundo plano acessem informações da área de transferência do aplicativo em primeiro plano, o que pode permitir o acesso direto a todos os dados copiados por agentes maliciosos.
  • No Android 12 e versões mais recentes (nível 31 da API), sempre que um aplicativo acessa dados na área de transferência e os cola, uma mensagem de aviso é mostrada ao usuário, dificultando que os ataques passem despercebidos. Além disso, para proteger as PII, o Android oferece suporte ao 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 simples e possam ser roubados por aplicativos maliciosos. A não implementação de um dos flags mencionados pode permitir que invasores exfiltrem dados sensíveis copiados para a área de transferência por meio de shoulder surfing ou aplicativos maliciosos que, enquanto são executados em segundo plano, fazem capturas de tela ou gravam vídeos das atividades de um usuário legítimo.

Impacto

A exploração do tratamento inadequado da área de transferência 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

Marcar 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 marcados 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 imposição da execução do app em versões do Android iguais ou posteriores ao Android 10 (API 29) impede que processos em segundo plano acessem dados da área de transferência no aplicativo em primeiro plano.

Para forçar a execução do app 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 ser executado em versões do Android anteriores ao Android 10 (nível da API 29), qualquer aplicativo em segundo plano poderá acessar dados da área de transferência. 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 é realizada 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 a seguir 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