Manejo seguro del portapapeles

Categoría de OWASP: MASVS-CODE: Calidad de código

Descripción general

Android ofrece un potente framework denominado portapapeles para copiar y pegar datos entre aplicaciones. Una implementación inadecuada de esta función podría exponer datos relacionados con el usuario a actores o aplicaciones maliciosos no autorizados.

El riesgo específico asociado con la exposición de los datos del portapapeles depende de la naturaleza de la aplicación y de la información de identificación personal (PII) que maneja. El impacto es especialmente alto para las aplicaciones financieras, ya que pueden exponer datos de pago o apps que manejan códigos de autenticación de dos factores (2FA).

Los vectores de ataque que se podrían aprovechar para filtrar datos del portapapeles varían según la versión de Android:

  • Las versiones de Android anteriores a Android 10 (nivel de API 29) permiten que las aplicaciones en segundo plano accedan a la información del portapapeles de la app en primer plano, lo que podría permitir el acceso directo a cualquier dato copiado por actores maliciosos.
  • A partir de Android 12 (nivel de API 31), cada vez que una aplicación accede a los datos del portapapeles y los pega, se muestra un mensaje de aviso al usuario, lo que dificulta que los ataques pasen desapercibidos. Además, para proteger la PII, Android admite la marca especial ClipDescription.EXTRA_IS_SENSITIVE o android.content.extra.IS_SENSITIVE. Esto permite a los desarrolladores ofuscar visualmente la vista previa del contenido del portapapeles dentro de la GUI del teclado, lo que evita que los datos copiados se muestren visualmente en texto sin formato y que las aplicaciones maliciosas los roben. Si no se implementa una de las marcas mencionadas anteriormente, los atacantes podrían filtrar datos sensibles copiados en el portapapeles, ya sea a través de la observación por encima del hombro o mediante aplicaciones maliciosas que, mientras se ejecutan en segundo plano, toman capturas de pantalla o graban videos de las actividades de un usuario legítimo.

Impacto

La explotación del manejo inadecuado del portapapeles podría provocar que actores maliciosos filtren datos sensibles o financieros relacionados con el usuario. Esto puede ayudar a los atacantes a realizar más acciones, como campañas de phishing o robo de identidad.

Mitigaciones

Marcar datos sensibles

Esta solución se emplea para ofuscar visualmente la vista previa del contenido del portapapeles dentro de la GUI del teclado. Cualquier dato sensible que se pueda copiar, como contraseñas o datos de tarjetas de crédito, debe marcarse con ClipDescription.EXTRA_IS_SENSITIVE o android.content.extra.IS_SENSITIVE antes de llamar a 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 las versiones más recientes de Android

Si se obliga a la app a ejecutarse en versiones de Android posteriores o iguales a Android 10 (API 29), se evita que los procesos en segundo plano accedan a los datos del portapapeles en la aplicación en primer plano.

Para obligar a la app a ejecutarse solo en Android 10 (API 29) o versiones posteriores, establece los siguientes valores para la configuración de la versión en los archivos de compilación de Gradle dentro de tu proyecto en 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"
          ...
      }
      ...
    }
    ...

Borrar el contenido del portapapeles después de un período definido

Si la aplicación está diseñada para ejecutarse en versiones de Android anteriores a Android 10 (nivel de API 29), cualquier aplicación en segundo plano puede acceder a los datos del portapapeles. Para reducir este riesgo, es útil implementar una función que borre cualquier dato copiado en el portapapeles después de un período específico. Esta función se realiza automáticamente a partir de Android 13 (nivel de API 33). Para versiones anteriores de Android, esta eliminación se puede realizar incluyendo el siguiente fragmento en el código de la aplicación.

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