Раскрытие информации в журнале

Категория OWASP: MASVS-STORAGE: Хранилище

Обзор

Раскрытие информации из логов — это тип уязвимости, при которой приложения записывают конфиденциальные данные в лог устройства. В случае попадания в руки злоумышленников эта конфиденциальная информация может представлять прямую ценность — например, учетные данные пользователя или персональные данные — или же может способствовать дальнейшим атакам.

Эта проблема может возникнуть в любом из следующих сценариев:

  • Журналы, сгенерированные приложением:
    • Журналы событий намеренно предоставляют доступ неавторизованным лицам, но при этом случайно содержат конфиденциальные данные.
    • В журналах намеренно содержатся конфиденциальные данные, но по случайности они могут быть доступны неавторизованным лицам.
    • Общие журналы ошибок, которые в зависимости от возникшего сообщения об ошибке могут иногда выводить конфиденциальные данные.
  • Журналы, сгенерированные извне:
    • Внешние компоненты отвечают за вывод журналов, содержащих конфиденциальные данные.

Операторы Android Log.* записывают данные в общий буфер памяти logcat . Начиная с Android 4.1 (уровень API 16), доступ к чтению logcat предоставляется только привилегированным системным приложениям путем объявления разрешения READ_LOGS . Однако Android поддерживает невероятно разнообразный набор устройств, предустановленные приложения которых иногда объявляют привилегию READ_LOGS . Вследствие этого, прямая запись логов в logcat не рекомендуется, поскольку она более подвержена утечке данных.

Убедитесь, что все данные, передаваемые в logcat , очищены в версиях вашего приложения, отличных от отладочных. Удалите все потенциально конфиденциальные данные. В качестве дополнительной меры предосторожности используйте такие инструменты, как R8, для удаления всех уровней логирования, кроме предупреждений и ошибок. Если вам нужны более подробные журналы, используйте внутреннее хранилище и управляйте своими собственными журналами напрямую, вместо использования системного журнала.

Влияние

Степень серьезности уязвимости класса «Раскрытие информации из журналов» может варьироваться в зависимости от контекста и типа конфиденциальных данных. В целом, последствия этой уязвимости заключаются в потере конфиденциальности потенциально важной информации, такой как персональные данные и учетные данные.

Меры по смягчению последствий

Общий

В качестве общей превентивной меры на этапах проектирования и внедрения следует устанавливать границы доверия в соответствии с принципом минимальных привилегий . В идеале, конфиденциальные данные не должны выходить за пределы каких-либо зон доверия. Это укрепляет принцип разделения привилегий.

Не записывайте конфиденциальные данные. По возможности записывайте только константы времени компиляции. Для аннотирования констант времени компиляции можно использовать инструмент ErrorProne .

Избегайте вывода в журналы сообщений, которые могут содержать непредвиденную информацию, включая конфиденциальные данные, в зависимости от возникшей ошибки. По возможности, данные, выводимые в журналы и журналы ошибок, должны содержать только предсказуемую информацию.

Избегайте ведения логов в logcat . Это связано с тем, что ведение логов в logcat может стать проблемой конфиденциальности из-за приложений с разрешением READ_LOGS . Кроме того, это неэффективно, поскольку не позволяет запускать оповещения или выполнять запросы. Мы рекомендуем приложениям настраивать бэкэнд logcat только для сборок разработчиков.

Большинство библиотек для управления логами позволяют задавать уровни логирования, что дает возможность записывать разный объем информации в отладочные и производственные логи. Измените уровень логирования так, чтобы он отличался от «отладочного» сразу после завершения тестирования продукта.

Удалите из производственной среды как можно больше уровней логирования. Если избежать ведения логов в производственной среде невозможно, удалите непостоянные переменные из операторов логирования. Могут возникнуть следующие ситуации:

  • Вы можете удалить все журналы из производственной среды.
  • В производственной среде необходимо вести журналы предупреждений и ошибок.

В обоих этих случаях удаляйте логи автоматически с помощью таких библиотек, как R8. Любые попытки удалить логи вручную чреваты ошибками. В рамках оптимизации кода R8 можно настроить на безопасное удаление уровней логирования, которые вы хотите сохранить для отладки, но удалить в производственной среде.

Если вы собираетесь вести ведение журналов в производственной среде, подготовьте флаги, которые можно использовать для условного отключения ведения журналов в случае инцидента. Флаги реагирования на инциденты должны отдавать приоритет следующим параметрам: безопасность развертывания; скорость и простота развертывания; тщательность редактирования журналов; использование памяти; и затраты на производительность при сканировании каждого сообщения журнала.

Удаление логов в logcat из производственных сборок с помощью R8.

В Android Studio 3.4 или плагине Android Gradle 3.4.0 и выше R8 является компилятором по умолчанию для оптимизации и сокращения кода. Однако вам необходимо включить R8 .

R8 заменил ProGuard, но файл правил в корневой папке проекта по-прежнему называется proguard-rules.pro . Следующий фрагмент кода демонстрирует пример файла proguard-rules.pro , который удаляет из производственной среды все логи, кроме предупреждений и ошибок:

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

Приведенный ниже пример файла proguard-rules.pro удаляет все журналы из рабочей среды:

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

Обратите внимание, что R8 предоставляет возможности сжатия приложений и функцию удаления логов. Если вы хотите использовать R8 только для функции удаления логов, добавьте следующее в файл proguard-rules.pro :

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

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

Проведите очистку всех журналов событий в производственной среде, содержащих конфиденциальные данные.

Во избежание утечки конфиденциальных данных убедитесь, что все сообщения в logcat очищены в версиях вашего приложения, отличных от отладочных. Удалите все данные, которые могут быть конфиденциальными.

Пример:

Котлин

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

Удалять конфиденциальные данные из журналов

Если вам необходимо включить конфиденциальные данные в журналы, мы рекомендуем очистить их перед печатью, чтобы удалить или замаскировать конфиденциальную информацию. Для этого используйте один из следующих методов:

  • Токенизация. Если конфиденциальные данные хранятся в хранилище, например, в системе управления шифрованием, где секреты могут быть доступны через токены, регистрируйте токен, а не конфиденциальные данные.
  • Маскирование данных. Маскирование данных — это односторонний необратимый процесс. Он создает версию конфиденциальных данных, которая структурно похожа на оригинал, но скрывает наиболее конфиденциальную информацию, содержащуюся в поле. Пример: замена номера кредитной карты 1234-5678-9012-3456 на XXXX-XXXX-XXXX-1313 . Перед выпуском приложения в производство мы рекомендуем провести проверку безопасности, чтобы тщательно изучить использование маскирования данных. Предупреждение: не используйте маскирование данных в случаях, когда даже раскрытие лишь части конфиденциальных данных может существенно повлиять на безопасность, например, при работе с паролями.
  • Редактирование. Редактирование похоже на маскирование, но скрывает всю информацию, содержащуюся в поле. Пример: замена номера кредитной карты 1234-5678-9012-3456 на XXXX-XXXX-XXXX-XXXX .
  • Фильтрация. Внедрите в выбранную вами библиотеку логирования строки форматирования, если они еще не существуют, чтобы упростить изменение непостоянных значений в сообщениях логов.

Печать логов должна осуществляться только с помощью компонента «очистка логов», который гарантирует очистку всех логов перед печатью, как показано в следующем фрагменте кода.

Котлин

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