Divulgação de informações de registro

Categoria do OWASP: MASVS-STORAGE - Armazenamento

Visão geral

A divulgação das informações de registro é um tipo de vulnerabilidade em que os apps mostram dados sensíveis no registro do dispositivo. Se forem expostas a agentes maliciosos, essas informações sensíveis podem ser valiosas imediatamente, como as credenciais ou as informações de identificação pessoal (PII) dos usuários, ou podem permitir outros ataques.

Esse problema pode ocorrer em qualquer um destes cenários:

  • Registros gerados pelo app:
    • Eles permitem intencionalmente o acesso de agentes não autorizados, mas contêm, por acidente, dados confidenciais.
    • Eles incluem dados sensíveis intencionalmente, mas são acessíveis para usuários não autorizados por acidente.
    • Registros de erros genéricos que podem mostrar dados confidenciais, dependendo da mensagem de erro acionada.
  • Registros gerados externamente:
    • Os componentes externos são responsáveis por mostrar registros que incluem dados confidenciais.

As instruções Log.* do Android são gravadas no buffer de memória comum logcat. No Android 4.1 (nível 16 da API) e versões mais recentes, apenas apps privilegiados do sistema podem receber acesso para ler logcat, declarando a permissão READ_LOGS. No entanto, o Android oferece suporte a um conjunto incrivelmente diverso de dispositivos cujos aplicativos pré-carregados às vezes declaram o privilégio READ_LOGS. Como consequência, a geração de registros diretamente em logcat não é recomendada, porque é mais propensa a vazamento de dados.

Garanta que toda a geração de registros para logcat seja limpa em versões não depuráveis do aplicativo. Remova todos os dados que possam ser confidenciais. Como precaução extra, use ferramentas como o R8 para remover todos os níveis de registro, exceto avisos e erros. Se você precisar de registros mais detalhados, use o armazenamento interno e gerencie seus próprios registros diretamente, em vez do registro do sistema.

Impacto

A gravidade da classe de vulnerabilidade de divulgação de informações de registro pode variar de acordo com o contexto e o tipo de dados sensíveis. No geral, o impacto dessa classe de vulnerabilidade é a perda de confidencialidade de informações potencialmente críticas, como PII e credenciais.

Mitigações

Geral

Como medida preventiva geral durante a criação e a implementação, estabeleça limites de confiança de acordo com o princípio de privilégio mínimo (link em inglês). O ideal é que os dados confidenciais não ultrapassem nem alcancem nenhuma das áreas de confiança. Isso reforça a separação de privilégios.

Não registre dados confidenciais. Registre sempre constantes de compilação sempre que possível. Você pode usar a ferramenta ErrorProne (em inglês) para anotações de constantes de compilação.

Evite registros que mostrem declarações que possam conter informações não previstas, incluindo dados confidenciais, dependendo do erro acionado. Os dados que aparecem nos registros e nos registros de erros só podem incluir informações previsíveis.

Evite fazer o registro no logcat. O registro no logcat pode se tornar um problema de privacidade devido a apps com a permissão READ_LOGS. Ele também não é eficiente porque não pode acionar alertas nem ser consultado. Recomendamos que os aplicativos configurem o back-end do logcat apenas para builds de desenvolvedor.

A maioria das bibliotecas de gerenciamento de registros permite definir níveis de registro e, então, registrar diferentes quantidades de informação entre os registros de depuração e de produção. Mude o nível de registro para que seja diferente de "debug" assim que o teste do produto terminar.

Remova o máximo dos níveis de registro possível da produção. Se não for possível manter os registros em produção, remova as variáveis não constantes dos log statements. Estes cenários podem ocorrer:

  • Todos os registros da produção podem ser removidos.
  • O registro de avisos e erros precisa ser mantido na produção.

Em ambos os casos, remova os registros automaticamente usando bibliotecas como o R8. Qualquer tentativa de remoção manual de registros está sujeita a erros. Como parte da otimização de código, o R8 pode ser configurado para remover com segurança os níveis de registro que você quer manter para depuração, mas remover na produção.

Para manter os registros na produção, prepare as flags que podem ser usadas para desligar a geração de registros condicionalmente em caso de incidentes. As flags de resposta a incidentes precisam priorizar o seguinte: segurança da implantação, velocidade e facilidade de implantação, edição minuciosa de registros, uso de memória e custos de performance da verificação de cada mensagem de registro.

Use o R8 para remover registros de builds de produção gravados no logcat.

No Android Studio 3.4 ou no Plug-in do Android para Gradle 3.4.0 e versões mais recentes, o R8 é o compilador padrão para otimização e redução de código. No entanto, você precisa ativar o R8.

O R8 substituiu o ProGuard, mas o arquivo de regras na pasta raiz do projeto ainda é chamado de proguard-rules.pro. O snippet a seguir mostra um exemplo de arquivo proguard-rules.pro que remove todos os registros da produção, exceto avisos e erros.

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

O exemplo de arquivo proguard-rules.pro abaixo remove todos os registros da produção:

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

Observe que o R8 oferece recursos de redução de apps e funcionalidade de remoção de registro. Se você quiser usar o R8 apenas para a funcionalidade de remoção de registro, adicione o seguinte ao seu arquivo proguard-rules.pro:

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

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

Limpe os registros finais da produção que contêm dados confidenciais

Para evitar o vazamento de dados confidenciais, verifique se toda a geração de registros para logcat está limpa em versões não depuráveis do seu aplicativo. Remova todos os dados que possam ser confidenciais.

Exemplo:

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

Edite dados confidenciais em registros

Se for necessário incluir dados confidenciais nos registros, recomendamos limpar os registros antes de mostrá-los para remover ou ofuscar dados confidenciais. Para fazer isso, use uma destas técnicas:

  • Tokenização. Se dados confidenciais estiverem armazenados em um cofre (como um sistema de gerenciamento de criptografia em que os Secrets possam ser referenciados com tokens), registre o token em vez dos dados sensíveis.
  • Mascaramento de dados. O mascaramento de dados é um processo unidirecional e irreversível. Ele cria uma versão dos dados confidenciais que é estruturalmente semelhante ao original, mas oculta as informações mais sensíveis contidas em um campo. Exemplo: substituir o número do cartão de crédito 1234-5678-9012-3456 por XXXX-XXXX-XXXX-1313. Antes de lançar o app para produção, recomendamos que você conclua um processo de análise de segurança para examinar melhor o uso da máscara de dados. Aviso: não use o mascaramento de dados nos casos em que a liberação de apenas uma parte dos dados sensíveis pode afetar significativamente a segurança, como o processamento de senhas.
  • Edição. A edição é semelhante ao mascaramento, mas oculta todas as informações contidas em um campo. Exemplo: substituir o número do cartão de crédito 1234-5678-9012-3456 por XXXX-XXXX-XXXX-XXXX.
  • Filtragem. Implemente strings de formato na sua biblioteca de geração de registros (se ainda não houver) para facilitar a modificação de valores não constantes em log statements.

A exibição de registros só deve ser feita por um componente "limpador de registros" que garanta que todos os registros sejam limpos antes de serem mostrados, conforme ilustrado no snippet de código abaixo.

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