Ujawnienie informacji dziennika

Kategoria OWASP: MASVS-STORAGE: Storage

Omówienie

Ujawnienie informacji z dziennika to typ podatności, w której aplikacje zapisują dane wrażliwe w dzienniku urządzenia. Jeśli w ręce złośliwych aktorów wpadną informacje poufne, takie jak dane logowania użytkownika lub informacje umożliwiające identyfikację, mogą one być bardzo cenne lub umożliwić przeprowadzenie kolejnych ataków.

Ten problem może wystąpić w tych sytuacjach:

  • Logi generowane przez aplikację:
    • Dzienniki celowo umożliwiają dostęp nieupoważnionym podmiotom, ale zawierają przypadkowo dane wrażliwe.
    • Logi zawierają celowo dane wrażliwe, ale są one przypadkowo dostępne dla nieupoważnionych osób.
    • Ogólne dzienniki błędów, które czasami mogą drukować dane poufne, w zależności od wywołanego komunikatu o błędzie.
  • Logi wygenerowane zewnętrznie:
    • Za logi drukowania zawierające dane poufne odpowiadają komponenty zewnętrzne.

Instrukcje Log.* na Androidzie zapisują dane do wspólnego bufora pamięci logcat. Od Androida 4.1 (poziom interfejsu API 16) tylko uprzywilejowane aplikacje systemowe mogą uzyskać dostęp do odczytu logcat, deklarując uprawnienie READ_LOGS. Android obsługuje jednak bardzo zróżnicowany zestaw urządzeń, których wstępnie załadowane aplikacje czasami deklarują uprawnienie READ_LOGS. Dlatego nie zalecamy rejestrowania bezpośrednio w logcat, ponieważ jest to bardziej podatne na wyciek danych.

Upewnij się, że wszystkie logowania do logcat są czyszczone w wersjach aplikacji bez debugowania. Usuń wszystkie dane, które mogą być poufne. Jako dodatkowy środek ostrożności użyj narzędzi takich jak R8, aby usunąć wszystkie poziomy logowania z wyjątkiem ostrzeżeń i błędów. Jeśli potrzebujesz bardziej szczegółowych dzienników, użyj pamięci wewnętrznej i bezpośrednio zarządzaj własnymi dziennikami zamiast używać dziennika systemowego.

Wpływ

Powaga luki w zabezpieczeniach w klasie Log Info Disclosure może się różnić w zależności od kontekstu i typu danych wrażliwych. Ogólnie rzecz biorąc, skutkiem tej klasy luki jest utrata poufności potencjalnie krytycznych informacji, takich jak informacje umożliwiające identyfikację i dane logowania.

Środki zaradcze

Ogólne

Podczas projektowania i wdrażania należy wyznaczyć granice zaufania zgodnie z zasadą jak najmniejszych uprawnień. W idealnej sytuacji dane wrażliwe nie powinny przekraczać granic obszarów zaufania ani docierać poza nie. W ten sposób wzmacniamy oddzielenie uprawnień.

Nie rejestruj danych wrażliwych. W miarę możliwości rejestruj tylko stałe wartości w czasie kompilacji. Aby dodać adnotacje stałych w czasie kompilacji, możesz użyć narzędzia ErrorProne.

Unikaj dzienników, które mogą drukować instrukcje zawierające nieoczekiwane informacje, w tym dane wrażliwe, w zależności od wywołanego błędu. W miarę możliwości dane drukowane w logach i logach błędów powinny zawierać tylko przewidywalne informacje.

Unikaj logowania na serwerze logcat. Dzieje się tak, ponieważ logowanie do logcat może stać się problemem związanym z prywatnością z powodu aplikacji z uprawnieniem READ_LOGS. Jest też nieskuteczna, ponieważ nie może wywoływać alertów ani być używana do zapytań. Zalecamy, aby aplikacje konfigurowały backend logcat tylko w przypadku wersji deweloperskich.

Większość bibliotek do zarządzania logami umożliwia definiowanie poziomów logowania, co pozwala na rejestrowanie różnych ilości informacji w logach debugowania i produkcyjnych. Zmień poziom logowania na inny niż „debug”, gdy tylko zakończy się testowanie produktu.

Usuń z produkcji jak najwięcej poziomów logowania. Jeśli nie możesz uniknąć przechowywania logów w produkcji, usuń z instrukcji logowania zmienne niestałe. Mogą wystąpić następujące scenariusze:

  • Możesz usunąć wszystkie dzienniki z wersji produkcyjnej.
  • Musisz zachować dzienniki ostrzeżeń i błędów w środowisku produkcyjnym.

W obu tych przypadkach usuwaj dzienniki automatycznie za pomocą bibliotek takich jak R8. W przypadku próby ręcznego usuwania dzienników istnieje ryzyko wystąpienia błędu. W ramach optymalizacji kodu możesz ustawić R8 tak, aby bezpiecznie usuwać poziomy logowania, które chcesz zachować na potrzeby debugowania, ale usuwać w produkcji.

Jeśli zamierzasz logować się w wersji produkcyjnej, przygotuj flagi, które pozwolą Ci warunkowo wyłączyć rejestrowanie w przypadku incydentu. Flagi odpowiedzi na incydenty powinny priorytetowo uwzględniać bezpieczeństwo wdrożenia, szybkość i łatwość wdrożenia, dokładność w usuwaniu danych z dzienników, wykorzystanie pamięci oraz koszty wydajności związane z skanowaniem każdego komunikatu z dziennika.

Użyj R8, aby usunąć logi z logcat z kompilacji produkcyjnych.

W Android Studio 3.4 lub wtyczce Androida do obsługi Gradle 3.4.0 i nowszych wersjach domyślnym kompilatorem do optymalizacji i skracania kodu jest R8. Musisz jednak włączyć R8.

R8 zastąpiło ProGuard, ale plik reguł w folderze głównym projektu nadal nazywa się proguard-rules.pro.Poniższy fragment kodu pokazuje przykładowy plik proguard-rules.pro, który usuwa z produkcji wszystkie dzienniki z wyjątkiem ostrzeżeń i błędów:

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

Ten przykładowy plik proguard-rules.pro usuwa wszystkie dzienniki z wersji produkcyjnej:

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

Pamiętaj, że R8 umożliwia kompresowanie aplikacji i usuwanie z nich logów. Jeśli chcesz używać R8 tylko do usuwania logów, dodaj do pliku proguard-rules.pro następujące polecenie:

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

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

Odczyść ewentualne dzienniki w wersji produkcyjnej zawierające dane wrażliwe

Aby uniknąć wycieku danych wrażliwych, sprawdź, czy wszystkie logi w usłudze logcat są czyszczone w wersjach aplikacji bez debugowania. Usuń wszystkie dane, które mogą być poufne.

Przykład:

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

Usuwanie danych wrażliwych z logów

Jeśli musisz uwzględnić w logach dane wrażliwe, zalecamy ich wyczyszczenie przed wydrukowaniem, aby usunąć lub zaciemnić dane wrażliwe. Aby to zrobić, użyj jednej z tych metod:

  • Tokenizacja. Jeśli dane wrażliwe są przechowywane w skarbcu, np. w systemie zarządzania szyfrowaniem, z którego można odwoływać się do obiektów tajnych za pomocą tokenów, zamiast danych wrażliwych należy rejestrować tokeny.
  • Maskowanie danych Maskowanie danych jest procesem jednokierunkowym i nieodwracalnym. Tworzy ona wersję danych poufnych, która pod względem struktury jest podobna do wersji oryginalnej, ale ukrywa najbardziej poufne informacje zawarte w polu. Przykład: zastąpienie numeru karty kredytowej 1234-5678-9012-3456 wartością XXXX-XXXX-XXXX-1313. Przed udostępnieniem aplikacji w wersji produkcyjnej zalecamy przeprowadzenie procesu weryfikacji bezpieczeństwa, aby dokładnie sprawdzić użycie maskowania danych. Ostrzeżenie: nie używaj maskowania danych w przypadkach, gdy nawet ujawnienie części danych wrażliwych może znacząco wpłynąć na bezpieczeństwo, np. podczas obsługi haseł.
  • Usunięcie. Usunięcie jest podobne do zamaskowania, ale ukrywa wszystkie informacje zawarte w polu. Przykład: zastąpienie numeru karty kredytowej 1234-5678-9012-3456 wartością XXXX-XXXX-XXXX-XXXX.
  • Filtrowanie. W wybranej przez siebie bibliotece do rejestrowania danych wprowadź ciągi znaków formatu, jeśli jeszcze nie istnieją, aby ułatwić modyfikowanie wartości niestałych w instrukcjach logowania.

Drukowanie logów powinno być wykonywane tylko przez komponent „logs sanitizer”, który zapewnia, że wszystkie logi są czyszczone przed wydrukowaniem, jak pokazano w tym fragmencie kodu.

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