Divulgation d'informations de journal

Catégorie OWASP : MASVS-STORAGE : stockage

Présentation

La divulgation d'informations de journal est un type de faille qui permet aux applications d'imprimer des données sensibles dans le journal de l'appareil. Lorsqu'elles sont exposées à des acteurs malintentionnés, ces informations sensibles peuvent être exploitées directement (telles que les identifiants ou les informations permettant d'identifier personnellement l'utilisateur) ou permettre d'autres attaques.

Ce problème peut survenir dans les scénarios suivants :

  • Journaux générés par l'application :
    • Journaux qui autorisent intentionnellement l'accès à des acteurs non autorisés, mais qui contiennent accidentellement des données sensibles
    • Journaux qui incluent intentionnellement des données sensibles, mais qui sont accessibles aux acteurs non autorisés par accident
    • Journaux d'erreurs génériques pouvant parfois imprimer des données sensibles, en fonction du message d'erreur déclenché
  • Journaux générés en externe :
    • Composants externes responsables de l'impression des journaux contenant des données sensibles

Les instructions Android Log.* écrivent les données dans le tampon de mémoire commun logcat. Depuis Android 4.1 (niveau d'API 16), seules les applications système privilégiées peuvent être autorisées à lire logcat, en déclarant l'autorisation READ_LOGS. Cependant, Android est compatible avec un ensemble incroyablement varié d'appareils dont les applications préchargées déclarent parfois le droit READ_LOGS. Par conséquent, nous vous déconseillons de procéder directement à la journalisation dans logcat, car les risques de fuite de données sont plus importants.

Assurez-vous que toutes les données journalisées dans logcat sont nettoyées dans les versions de votre application qui ne sont pas destinées au débogage. Supprimez toutes les données qui pourraient être sensibles. Par mesure de précaution supplémentaire, utilisez des outils tels que R8 pour supprimer tous les niveaux de journalisation, à l'exception des avertissements et des erreurs. Si vous avez besoin de journaux plus détaillés, utilisez la mémoire de stockage interne et gérez directement vos propres journaux au lieu de recourir au journal système.

Impact

La gravité de la classe de faille de divulgation des informations de journal peut varier en fonction du contexte et du type de données sensibles. Globalement, l'impact de cette classe de faille est la perte de confidentialité d'informations potentiellement critiques telles que des informations permettant d'identifier l'utilisateur et des identifiants de connexion.

Stratégies d'atténuation

Général

En tant que mesure préventive générale lors de la conception et de la mise en œuvre, définissez des limites de confiance en suivant le principe du moindre privilège. Idéalement, les données sensibles ne doivent pas se trouver en dehors d'un domaine de confiance. Cette approche renforce la séparation des droits.

Ne journalisez pas les données sensibles. Dans la mesure du possible, journalisez uniquement les constantes de compilation. Vous pouvez utiliser l'outil ErrorProne pour l'annotation des constantes de compilation.

Évitez les journaux qui impriment des instructions susceptibles de contenir des informations imprévues, y compris des données sensibles, en fonction de l’erreur déclenchée. Dans la mesure du possible, les données imprimées dans les journaux standards et les journaux d'erreurs ne doivent inclure que des informations prévisibles.

Évitez la journalisation dans logcat. En effet, la journalisation dans logcat peut entraîner des problèmes de confidentialité à cause des applications disposant de l'autorisation READ_LOGS. De plus, elle est inefficace, car elle ne peut pas déclencher d'alertes ni faire l'objet de requêtes. Nous vous recommandons de ne configurer le backend logcat que pour les versions de développement.

La plupart des bibliothèques de gestion des journaux permettent de définir des niveaux de journalisation, afin d'enregistrer différentes quantités d'informations entre les journaux de débogage et de production. Modifiez le niveau de journalisation de sorte qu'il soit différent du niveau "débogage" dès la fin des tests du produit.

Supprimez autant de niveaux de journalisation que possible. Si vous devez à tout prix conserver des journaux en production, supprimez les variables non constantes des instructions de journalisation. Voici les différents scénarios possibles :

  • Vous pouvez supprimer tous les journaux de la production.
  • Vous devez conserver les journaux d'avertissements et d'erreurs en production.

Dans les deux cas, supprimez automatiquement les journaux à l'aide de bibliothèques telles que R8. Les tentatives de suppression manuelle des journaux entraînent souvent des erreurs. Dans le cadre de l'optimisation du code, vous pouvez configurer R8 de manière à supprimer en toute sécurité les niveaux de journalisation que vous souhaitez conserver pour le débogage, mais pas pour la production.

Si vous comptez conserver des journaux en production, préparez des indicateurs que vous pourrez utiliser pour arrêter la journalisation de manière conditionnelle en cas d'incident. Ces indicateurs de réponse aux incidents doivent se concentrer sur les critères suivants : sécurité du déploiement, vitesse et facilité du déploiement, minutie du masquage des journaux, utilisation de la mémoire et coûts de performance liés à l'analyse de chaque message de journal.

Supprimer les journaux Logcat des builds de production à l'aide de R8

Dans Android Studio 3.4 ou le plug-in Android Gradle 3.4.0 ou version ultérieure, R8 est le compilateur par défaut pour l'optimisation et la minification du code. Vous devez toutefois activer R8.

R8 remplace ProGuard, mais le fichier de règles dans le dossier racine du projet s'appelle toujours proguard-rules.pro. L'extrait suivant montre un exemple de fichier proguard-rules.pro qui supprime tous les journaux de production, sauf les avertissements et les erreurs :

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

L'exemple de fichier proguard-rules.pro suivant supprime tous les journaux de la production :

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

Notez que R8 offre des fonctionnalités de minification d'application et de suppression des journaux. Si vous souhaitez n'utiliser R8 que pour sa fonctionnalité de suppression des journaux, ajoutez ce qui suit à votre fichier proguard-rules.pro :

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

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

Nettoyer tous les journaux éventuels contenant des données sensibles en production

Pour éviter toute fuite de données sensibles, assurez-vous que toutes les journalisations dans logcat sont nettoyées dans les versions de votre application qui ne sont pas destinées au débogage. Supprimez toutes les données qui pourraient être sensibles.

Exemple :

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

Masquer les données sensibles dans les journaux

Si vous devez inclure des données sensibles dans vos journaux, nous vous recommandons de nettoyer les journaux avant de les imprimer afin de supprimer ou d'obscurcir les données sensibles. Pour ce faire, optez pour l'une des techniques suivantes :

  • Tokenization : si des données sensibles sont stockées dans un endroit sécurisé, par exemple un système de gestion du chiffrement permettant de référencer les secrets à l'aide de jetons, enregistrez le jeton plutôt que les données sensibles.
  • Masquage des données : le masquage des données est un processus irréversible. Il crée une version des données sensibles qui est semblable à la structure d'origine, mais masque les informations les plus sensibles contenues dans un champ (par exemple, en remplaçant le numéro de carte de crédit 1234-5678-9012-3456 par XXXX-XXXX-XXXX-1313). Avant de publier votre application en production, nous vous recommandons de vérifier la sécurité en vue d'examiner minutieusement l'utilisation du masquage des données. Avertissement : n'utilisez pas le masquage des données dans les cas où la diffusion ne serait-ce que d'une partie des données sensibles pourrait avoir un impact important sur la sécurité, par exemple lors de la gestion des mots de passe.
  • Masquage complet : le masquage complet est semblable au masquage des données mentionné ci-dessus, mais toutes les informations contenues dans le champ sont masquées (par exemple, en remplaçant le numéro de carte de crédit 1234-5678-9012-3456 par XXXX-XXXX-XXXX-XXXX).
  • Filtrage : implémentez des chaînes de format dans la bibliothèque de journalisation de votre choix (si elles n'existent pas déjà) afin de faciliter la modification des valeurs non constantes dans les instructions de journalisation.

L'impression des journaux ne doit être effectuée que par l'intermédiaire d'un composant qui garantit que tous les journaux sont nettoyés avant d'être imprimés, comme illustré dans l'extrait de code suivant.

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