Kategoria OWASP: MASVS-STORAGE: Storage
Przegląd
Ujawnianie informacji z dziennika urządzenia to rodzaj luki w zabezpieczeniach, w której aplikacje zapisują dane wrażliwe w dzienniku urządzenia. Jeśli te informacje trafią w ręce złośliwych podmiotów, mogą być dla nich cenne – np. dane logowania użytkownika lub informacje umożliwiające identyfikację osób – albo mogą umożliwić im przeprowadzenie dalszych ataków.
Ten problem może wystąpić w tych sytuacjach:
- Logi generowane przez aplikację:
- Logi celowo umożliwiają dostęp nieautoryzowanym podmiotom, ale przypadkowo zawierają dane wrażliwe.
- Logi celowo zawierają dane wrażliwe, ale przypadkowo są dostępne dla nieautoryzowanych podmiotów.
- Ogólne logi błędów, które w zależności od wywołanego komunikatu o błędzie mogą czasami zawierać dane wrażliwe.
- Logi generowane przez podmioty zewnętrzne:
- Za zapisywanie logów zawierających dane wrażliwe odpowiadają komponenty zewnętrzne.
Instrukcje Log.* w Androidzie zapisują dane w wspólnym buforze pamięci logcat. Od Androida 4.1 (poziom API 16) tylko aplikacje systemowe z podwyższonymi uprawnieniami mogą uzyskać dostęp do odczytu logcat przez zadeklarowanie uprawnienia READ_LOGS. Android obsługuje jednak bardzo wiele urządzeń, na których preinstalowane aplikacje czasami deklarują uprawnienie READ_LOGS. W związku z tym odradzamy logowanie bezpośrednio do logcat, ponieważ jest ono bardziej podatne na wyciek danych.
Upewnij się, że wszystkie logi zapisywane w logcat są czyszczone w wersjach aplikacji innych niż debugowanie. Usuń wszystkie dane, które mogą być poufne. Dodatkowo użyj narzędzi takich jak R8, aby usunąć wszystkie poziomy logów oprócz ostrzeżeń i błędów. Jeśli potrzebujesz bardziej szczegółowych logów, użyj pamięci wewnętrznej i zarządzaj logami bezpośrednio, zamiast korzystać z logów systemowych.
Wpływ
Stopień zagrożenia wynikającego z luki w zabezpieczeniach związanej z ujawnianiem informacji z logów może się różnić w zależności od kontekstu i rodzaju danych wrażliwych. Ogólnie rzecz biorąc, skutkiem tej luki jest utrata poufności potencjalnie krytycznych informacji, takich jak informacje umożliwiające identyfikację osób i dane logowania.
Środki zaradcze
Ogólne
Jako ogólny środek zapobiegawczy podczas projektowania i wdrażania wyznacz granice zaufania zgodnie z zasadą jak najmniejszych uprawnień. Dane wrażliwe nie powinny przekraczać granic żadnego z obszarów zaufania ani się poza nie wydostawać. Wzmocni to rozdzielenie uprawnień.
Nie zapisuj danych wrażliwych w logach. Jeśli to możliwe, zapisuj w logach tylko stałe czasu kompilacji. Do adnotacji stałych czasu kompilacji możesz użyć narzędzia ErrorProne.
Unikaj logów, które zawierają instrukcje drukowania mogące zawierać nieprzewidziane 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 do logcat. Logowanie do logcat może stanowić problem z prywatnością ze względu na aplikacje z uprawnieniem READ_LOGS. Jest też nieskuteczne, ponieważ nie może wywoływać alertów ani być przeszukiwane. Zalecamy, aby aplikacje konfigurowały backend logcat tylko w przypadku wersji dla deweloperów.
Większość bibliotek zarządzania logami umożliwia definiowanie poziomów logów, co pozwala na logowanie różnych ilości informacji w logach debugowania i logach produkcyjnych. Po zakończeniu testowania produktu zmień poziom logów tak, aby różnił się od poziomu „debug”.
Usuń z wersji produkcyjnej jak najwięcej poziomów logów. Jeśli nie możesz uniknąć przechowywania logów w wersji produkcyjnej, usuń z instrukcji logów zmienne inne niż stałe. Mogą wystąpić te sytuacje:
- Możesz usunąć wszystkie logi z wersji produkcyjnej.
- Musisz zachować w wersji produkcyjnej logi ostrzeżeń i błędów.
W obu tych przypadkach usuwaj logi automatycznie za pomocą bibliotek takich jak R8. Wszelkie próby ręcznego usuwania logów są podatne na błędy. W ramach optymalizacji kodu można skonfigurować R8 tak, aby bezpiecznie usuwał poziomy logów, które chcesz zachować do debugowania, ale usunąć w wersji produkcyjnej.
Jeśli zamierzasz logować w wersji produkcyjnej, przygotuj flagi, których możesz użyć do warunkowego wyłączenia logowania w przypadku incydentu. Flagi reagowania na incydenty powinny priorytetowo traktować bezpieczeństwo wdrożenia, szybkość i łatwość wdrożenia, dokładność usuwania logów, wykorzystanie pamięci oraz koszty wydajności związane ze skanowaniem każdego komunikatu logu.
Usuwanie logów z `logcat` w wersjach produkcyjnych za pomocą R8
W Android Studio 3.4 lub wtyczce Androida do obsługi Gradle w wersji 3.4.0 i nowszej R8 jest domyślnym kompilatorem do optymalizacji i zmniejszania kodu. Musisz jednak włączyć R8.
R8 zastąpił ProGuard, ale plik reguł w folderze głównym projektu nadal nazywa się proguard-rules.pro.Ten fragment kodu pokazuje przykładowy plik proguard-rules.pro, który usuwa wszystkie logi z wersji produkcyjnej oprócz 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 logi 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 zmniejszanie rozmiaru aplikacji i usuwanie logów. Jeśli chcesz używać R8 tylko do usuwania logów, dodaj do pliku proguard-rules.pro ten kod:
-dontwarn **
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!code/allocation/variable
-keep class **
-keepclassmembers class *{*;}
-keepattributes *
Czyszczenie ewentualnych logów w wersji produkcyjnej zawierających dane wrażliwe
Aby uniknąć wycieku danych wrażliwych, upewnij się, że wszystkie logi zapisywane w logcat są czyszczone w wersjach aplikacji innych niż debugowanie. 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 czyszczenie 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, do którego można się odwoływać za pomocą tokenów, zapisz w logach token zamiast danych wrażliwych.
- Maskowanie danych Maskowanie danych to proces jednokierunkowy i nieodwracalny. Tworzy wersję danych wrażliwych, która jest strukturalnie podobna do oryginału, ale ukrywa najbardziej wrażliwe informacje zawarte w polu. Przykład: zastąpienie numeru karty kredytowej
1234-5678-9012-3456ciągiemXXXX-XXXX-XXXX-1313. Zanim opublikujesz aplikację w wersji produkcyjnej, zalecamy przeprowadzenie procesu sprawdzania bezpieczeństwa, aby dokładnie sprawdzić użycie maskowania danych. Ostrzeżenie: nie używaj maskowania danych w przypadkach, gdy nawet udostępnienie tylko części danych wrażliwych może znacząco wpłynąć na bezpieczeństwo, np. podczas obsługi haseł. - Usuwanie Usuwanie jest podobne do maskowania, ale ukrywa wszystkie informacje zawarte w polu. Przykład: zastąpienie numeru karty kredytowej
1234-5678-9012-3456ciągiemXXXX-XXXX-XXXX-XXXX. - Filtrowanie Jeśli nie istnieją jeszcze ciągi formatujące, zaimplementuj je w wybranej bibliotece logowania, aby ułatwić modyfikowanie wartości innych niż stałe w instrukcjach logów.
Drukowanie logów powinno odbywać się tylko za pomocą komponentu „czyszczenia logów”, 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());
}