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