Divulgación de información de registros

Categoría de OWASP: MASVS-STORAGE: Almacenamiento

Descripción general

La divulgación de información de registro es un tipo de vulnerabilidad en el que las apps imprimen datos sensibles en el registro del dispositivo. Si se expone a personas malintencionadas, esta información sensible puede ser verdaderamente valiosa, como las credenciales o la información de identificación personal (PII) de un usuario, o bien puede facilitar nuevos ataques.

Este problema puede ocurrir en cualquiera de los siguientes casos:

  • Registros generados por la app:
    • Los registros permiten el acceso a personas no autorizadas de manera intencional, pero contienen datos sensibles por accidente.
    • Los registros incluyen datos sensibles de manera intencional, pero permiten que personas no autorizadas pueden acceder a ellas por accidente.
    • Registros de errores genéricos que, a veces, pueden imprimir datos sensibles, según el mensaje de error activado.
  • Registros generados de forma externa:
    • Los componentes externos son responsables de imprimir registros que incluyen datos sensibles.

Las sentencias Log.* de Android se escriben en el búfer de memoria común logcat. A partir de Android 4.1 (nivel de API 16), solo se puede otorgar acceso a las apps del sistema privilegiadas para que lean logcat mediante la declaración del permiso READ_LOGS. Sin embargo, Android admite un conjunto muy diverso de dispositivos cuyas aplicaciones precargadas a veces declaran el privilegio READ_LOGS. Como consecuencia, no se recomienda acceder directamente a logcat, ya que es más propenso a la filtración de datos.

Asegúrate de que todos los registros de logcat estén limpios en las versiones de no depuración de tu aplicación. Quita todos los datos que puedan ser sensibles. Como precaución adicional, usa herramientas, como R8, para quitar todos los niveles de registro, excepto las advertencias y los errores. Si necesitas registros más detallados, usa el almacenamiento interno y administra tus propios registros de forma directa, en lugar de usar el registro del sistema.

Impacto

La gravedad de la clase de vulnerabilidad de divulgación de información de registro puede variar según el contexto y el tipo de datos sensibles. En general, el impacto de esta clase de vulnerabilidad es la pérdida de confidencialidad de la información potencialmente crítica, como la PII y las credenciales.

Mitigaciones

General

Como medida preventiva general, durante el diseño y la implementación, establece límites de confianza según el principio de privilegio mínimo. Idealmente, los datos sensibles no deben cruzarse ni alcanzarse por fuera de ninguna de las áreas de confianza. Esto refuerza la separación de privilegios.

No registres datos sensibles. Solo registra constantes de tiempo de compilación siempre que sea posible. Puedes usar la herramienta ErrorProne para la anotación constante de tiempo de compilación.

Evita los registros que impriman sentencias que puedan contener información inesperada, como datos sensibles, en función del error activado. En la medida de lo posible, los datos impresos en los registros y los registros de errores solo deben incluir información predecible.

Evita acceder a logcat. Esto se debe a que el acceso a logcat puede convertirse en un problema de privacidad debido a las apps que tienen el permiso READ_LOGS. Tampoco es eficaz, ya que no puede activar alertas ni consultarse. Recomendamos que las aplicaciones configuren el backend logcat solo para compilaciones de desarrollador.

La mayoría de las bibliotecas de administración de registros admiten la definición de niveles de registro, lo que permite registrar diferentes cantidades de información entre los registros de depuración y de producción. Cambia el nivel de registro para que sea diferente de "debug" en cuanto finalice la prueba del producto.

Quita tantos niveles de registro de producción como sea posible. Si no puedes evitar que los registros se mantengan en producción, quita variables no constantes de las instrucciones de registro. Pueden ocurrir las siguientes situaciones:

  • Puedes quitar todos los registros de producción.
  • Debes conservar los registros de advertencia y error en producción.

En ambos casos, quita los registros automáticamente con bibliotecas como R8. Los intentos de quitar los registros de forma manual son propensos a generar errores. Como parte de la optimización de código, se puede configurar R8 para que quite de forma segura los niveles de registro que deseas conservar para la depuración, pero eliminar de la producción.

Si quieres acceder a la producción, prepara las marcas que puedes usar para cerrar el registro de forma condicional en caso de un incidente. Las marcas de respuesta ante incidentes deben priorizar lo siguiente: seguridad de la implementación, velocidad y facilidad de la implementación, profundidad de los registros, información sobre el uso de la memoria y costos de rendimiento de análisis de todos los mensajes de registro.

Quita registros para logcat desde compilaciones de producción mediante R8

En Android Studio 3.4 o el complemento de Android para Gradle 3.4.0 y versiones posteriores, R8 es el compilador predeterminado de reducción y optimización de código. Sin embargo, debes habilitar R8.

R8 reemplazó a ProGuard, pero el archivo de reglas en la carpeta raíz del proyecto aún se llama proguard-rules.pro. En el siguiente fragmento, se muestra un archivo proguard-rules.pro de ejemplo que quita todos los registros de producción, excepto las advertencias y los errores:

-assumenosideeffects class android.util.Log {
    private static final String TAG = "MyTAG";
    public static boolean isLoggable(java.lang.String, int);
    public static int v(TAG, "My log as verbose");
    public static int d(TAG, "My log as debug");
    public static int i(TAG, "My log as information");
}

En el siguiente archivo proguard-rules.pro de ejemplo, se quitan todos los registros de producción:

-assumenosideeffects class android.util.Log {
    private static final String TAG = "MyTAG";
    public static boolean isLoggable(java.lang.String, int);
    public static int v(TAG, "My log as verbose");
    public static int d(TAG, "My log as debug");
    public static int i(TAG, "My log as information");
    public static int w(TAG, "My log as warning");
    public static int e(TAG, "My log as error");
}

Ten en cuenta que R8 ofrece funciones de reducción de apps y eliminación de registros. Si deseas usar R8 solo por su funcionalidad de eliminación de registros, agrega lo siguiente a tu archivo proguard-rules.pro:

-dontwarn **
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose

-optimizations !code/simplification/arithmetic,!code/allocation/variable
-keep class **
-keepclassmembers class *{*;}
-keepattributes *

Limpia los registros eventuales de producción que contengan datos sensibles

Para evitar que se filtren datos sensibles, asegúrate de que todos los registros de logcat se limpien en las versiones de tu aplicación que no sean de depuración. Quita todos los datos que puedan ser sensibles.

Ejemplo:

Kotlin

data class Credential<T>(val data: String) {
  /** Returns a redacted value to avoid accidental inclusion in logs. */
  override fun toString() = "Credential XX"
}

fun checkNoMatches(list: List<Any>) {
    if (!list.isEmpty()) {
          Log.e(TAG, "Expected empty list, but was %s", list)
    }
}

Java

public class Credential<T> {
  private T t;
  /** Returns a redacted value to avoid accidental inclusion in logs. */
  public String toString(){
         return "Credential XX";
  }
}

private void checkNoMatches(List<E> list) {
   if (!list.isEmpty()) {
          Log.e(TAG, "Expected empty list, but was %s", list);
   }
}

Oculta datos sensibles en los registros

Si debes incluir datos sensibles en tus registros, te recomendamos que limpies los registros antes de imprimirlos para quitar o bien ofuscar los datos sensibles. Para ello, utiliza una de las siguientes técnicas:

  • Asignación de token: Si los datos sensibles se almacenan en una bóveda, como un sistema de administración de encriptación desde el que se puede hacer referencia a los secretos con tokens, regístralos en lugar de los sensibles.
  • Enmascaramiento de datos: El enmascaramiento de datos es un proceso unidireccional irreversible. Crea una versión de los datos sensibles que tiene una estructura similar a la original, pero oculta la información más sensible de un campo. Ejemplo: Sustituye el número de tarjeta de crédito 1234-5678-9012-3456 por XXXX-XXXX-XXXX-1313. Antes de lanzar tu app a producción, te recomendamos que completes un proceso de revisión de seguridad para analizar el uso del enmascaramiento de datos. Advertencia: No uses el enmascaramiento de datos en casos en los que incluso la liberación de solo una parte de los datos sensibles pueda afectar la seguridad de forma significativa, como cuando se manipulan contraseñas.
  • Ocultamiento: El ocultamiento es similar al enmascaramiento, pero oculta toda la información que contiene un campo. Ejemplo: Sustituye el número de tarjeta de crédito 1234-5678-9012-3456 por XXXX-XXXX-XXXX-XXXX.
  • Filtrado: Implementa cadenas de formato en la biblioteca de registro que elijas, si es que todavía no existen, para facilitar la modificación de valores no constantes en las instrucciones de registro.

Solo se debe realizar la impresión de registros a través de un componente de "limpieza de registros", que garantiza la limpieza de todos los registros antes de su impresión, como se muestra en el siguiente fragmento de código.

Kotlin

data class ToMask<T>(private val data: T) {
  // Prevents accidental logging when an error is encountered.
  override fun toString() = "XX"

  // Makes it more difficult for developers to invoke sensitive data
  // and facilitates sensitive data usage tracking.
  fun getDataToMask(): T = data
}

data class Person(
  val email: ToMask<String>,
  val username: String
)

fun main() {
    val person = Person(
        ToMask("name@gmail.com"),
        "myname"
    )
    println(person)
    println(person.email.getDataToMask())
}

Java

public class ToMask<T> {
  // Prevents accidental logging when an error is encountered.
  public String toString(){
         return "XX";
  }

  // Makes it more difficult for developers to invoke sensitive data
  // and facilitates sensitive data usage tracking.
  public T  getDataToMask() {
    return this;
  }
}

public class Person {
  private ToMask<String> email;
  private String username;

  public Person(ToMask<String> email, String username) {
    this.email = email;
    this.username = username;
  }
}

public static void main(String[] args) {
    Person person = new Person(
        ToMask("name@gmail.com"),
        "myname"
    );
    System.out.println(person);
    System.out.println(person.email.getDataToMask());
}