Android ma wbudowane funkcje zabezpieczeń, które znacznie zmniejszają częstotliwość występowania problemów z bezpieczeństwem aplikacji i ich wpływ. System jest zaprojektowany tak, aby aplikacje można było zwykle tworzyć z domyślnymi uprawnieniami systemowymi i uprawnieniami do plików, co pozwala uniknąć trudnych decyzji dotyczących bezpieczeństwa.
Te podstawowe funkcje zabezpieczeń pomagają tworzyć bezpieczne aplikacje:
- Piaskownica aplikacji na Androida, która izoluje dane i kod aplikacji od innych aplikacji.
- Platforma aplikacji z solidnymi implementacjami typowych funkcji zabezpieczeń, takich jak kryptografia, uprawnienia i bezpieczna komunikacja międzyprocesowa (IPC).
- Technologie takie jak randomizacja układu przestrzeni adresowej (ASLR), NX, ProPolice, safe_iop, OpenBSD
dlmallocicallocoraz Linuxmmap_min_addr, które pomagają ograniczać ryzyko związane z częstymi błędami zarządzania pamięcią. - Uprawnienia przyznawane przez użytkownika, które ograniczają dostęp do funkcji systemu i danych użytkownika.
- Uprawnienia zdefiniowane przez aplikację, które umożliwiają kontrolowanie danych aplikacji w przypadku każdej aplikacji z osobna.
Warto zapoznać się ze sprawdzonymi metodami ochrony Androida na tej stronie. Stosowanie tych praktyk jako ogólnych nawyków programistycznych pomaga uniknąć przypadkowego wprowadzenia problemów z bezpieczeństwem, które negatywnie wpływają na użytkowników.
Uwierzytelnianie
Uwierzytelnianie jest warunkiem wstępnym wielu kluczowych działań zabezpieczających. Aby kontrolować dostęp do chronionych zasobów, takich jak dane użytkownika, funkcje aplikacji i inne zasoby, musisz dodać do aplikacji na Androida uwierzytelnianie.
Możesz zwiększyć wygodę użytkowników podczas uwierzytelniania, integrując aplikację z Menedżerem danych logowania. Credential Manager to biblioteka Androida Jetpack, która ujednolica obsługę interfejsu API dla większości głównych metod uwierzytelniania, w tym kluczy dostępu, haseł i rozwiązań logowania federacyjnego, takich jak Zaloguj się przez Google.
Aby jeszcze bardziej zwiększyć bezpieczeństwo aplikacji, rozważ dodanie metod uwierzytelniania biometrycznego, takich jak skanowanie odcisków palców lub rozpoznawanie twarzy. Dobrymi kandydatami do dodania uwierzytelniania biometrycznego mogą być aplikacje do zarządzania finansami, opieki zdrowotnej lub tożsamością.
Platforma autouzupełniania na Androidzie może ułatwić proces rejestracji i logowania, zmniejszając liczbę błędów i trudności dla użytkowników. Automatyczne wypełnianie jest zintegrowane z menedżerami haseł, co umożliwia użytkownikom wybieranie złożonych, losowych haseł, które można łatwo i bezpiecznie przechowywać oraz odzyskiwać.
Integralność aplikacji
Interfejs Play Integrity API pomaga sprawdzać, czy interakcje i żądania serwera pochodzą z autentycznego pliku binarnego aplikacji uruchomionego na oryginalnym urządzeniu z Androidem. Wykrywając potencjalnie ryzykowne i fałszywe interakcje, np. pochodzące ze zmodyfikowanych wersji aplikacji i niezaufanych środowisk, serwer backendu aplikacji może podejmować odpowiednie działania, które mają na celu zapobieganie atakom i ograniczanie nadużyć.
Przechowywanie danych
Najczęstszym problemem związanym z bezpieczeństwem aplikacji na Androida jest to, czy dane zapisane na urządzeniu są dostępne dla innych aplikacji. Dane na urządzeniu możesz zapisać na 3 sposoby:
- Pamięć wewnętrzna
- Pamięć zewnętrzna
- Dostawcy treści
W sekcjach poniżej opisujemy problemy z bezpieczeństwem związane z każdym z tych podejść.
Pamięć wewnętrzna
Domyślnie pliki tworzone w pamięci wewnętrznej są dostępne tylko dla Twojej aplikacji. Android zapewnia tę ochronę, która jest wystarczająca w przypadku większości aplikacji.
Unikaj wycofanych trybów MODE_WORLD_WRITEABLE i MODE_WORLD_READABLE w przypadku plików IPC. Nie umożliwiają one ograniczenia dostępu do danych do określonych aplikacji ani nie zapewniają kontroli nad formatem danych. Jeśli chcesz udostępniać dane innym procesom aplikacji, rozważ użycie dostawcy treści, który oferuje uprawnienia do odczytu i zapisu innym aplikacjom i może dynamicznie przyznawać uprawnienia w poszczególnych przypadkach.
Pamięć zewnętrzna
Pliki utworzone w pamięci zewnętrznej, np. na kartach SD, można odczytywać i zapisywać globalnie. Ponieważ użytkownik może usunąć zewnętrzną pamięć masową, a każda aplikacja może ją modyfikować, w zewnętrznej pamięci masowej przechowuj tylko informacje nieobjęte ochroną.
Przeprowadzaj weryfikację danych wejściowych podczas obsługi danych z pamięci zewnętrznej, tak jak w przypadku danych z dowolnego niezaufanego źródła. Nie przechowuj plików wykonywalnych ani plików klas na pamięci zewnętrznej przed dynamicznym wczytaniem. Jeśli aplikacja pobiera pliki wykonywalne z pamięci zewnętrznej, przed dynamicznym wczytaniem upewnij się, że są one podpisane i zweryfikowane kryptograficznie.
Dostawcy treści
Dostawcy treści oferują mechanizm strukturalnego przechowywania, który może być ograniczony do Twojej aplikacji lub eksportowany, aby umożliwić dostęp innym aplikacjom. Jeśli nie zamierzasz udostępniać ContentProvider innym aplikacjom, oznacz je w pliku manifestu aplikacji jako android:exported=false. W przeciwnym razie ustaw atrybut android:exported na true, aby umożliwić innym aplikacjom dostęp do przechowywanych danych.
Podczas tworzenia ContentProvider, który jest eksportowany do użytku w innych aplikacjach, możesz określić jedno uprawnienie do odczytu i zapisu lub osobne uprawnienia do odczytu i zapisu. Ogranicz uprawnienia do tych, które są wymagane do wykonania danego zadania. Pamiętaj, że zwykle łatwiej jest później dodać uprawnienia, aby udostępnić nowe funkcje, niż je odebrać i wpłynąć na obecnych użytkowników.
Jeśli korzystasz z dostawcy treści do udostępniania danych tylko między własnymi aplikacjami, zalecamy użycie zestawu atrybutów android:protectionLevel ustawionego na signature protection. Uprawnienia sygnatury nie wymagają potwierdzenia przez użytkownika, dzięki czemu zapewniają lepszą obsługę i bardziej kontrolowany dostęp do danych dostawcy treści, gdy aplikacje uzyskujące dostęp do danych są podpisane tym samym kluczem.
Dostawcy treści mogą też zapewnić bardziej szczegółowy dostęp, deklarując atrybut android:grantUriPermissions i używając flag FLAG_GRANT_READ_URI_PERMISSION i FLAG_GRANT_WRITE_URI_PERMISSION w obiekcie Intent, który aktywuje komponent. Zakres tych uprawnień można dodatkowo ograniczyć za pomocą elementu <grant-uri-permission>.
Podczas uzyskiwania dostępu do dostawcy treści używaj sparametryzowanych metod zapytań, takich jak query, update i delete(), aby uniknąć potencjalnego wstrzyknięcia kodu SQL z niezaufanych źródeł. Pamiętaj, że używanie metod sparametryzowanych nie wystarczy, jeśli argument selection jest tworzony przez łączenie danych użytkownika przed przesłaniem ich do metody.
Nie daj się zwieść fałszywemu poczuciu bezpieczeństwa związanemu z uprawnieniami do zapisu. Uprawnienie write umożliwia wykonywanie instrukcji SQL, które pozwalają potwierdzać niektóre dane za pomocą klauzul WHERE w kreacjach i analizować wyniki. Na przykład
osoba atakująca może sprawdzać, czy w dzienniku połączeń znajduje się konkretny numer telefonu, modyfikując wiersz tylko wtedy, gdy ten numer już istnieje. Jeśli dane dostawcy treści mają przewidywalną strukturę, uprawnienia do zapisu mogą być równoznaczne z uprawnieniami do odczytu i zapisu.
Uprawnienia
Aplikacje na Androida są od siebie odseparowane, dlatego muszą wyraźnie udostępniać sobie zasoby i dane. Deklarują one uprawnienia, których potrzebują do korzystania z dodatkowych funkcji niedostępnych w podstawowym środowisku piaskownicy, w tym dostępu do funkcji urządzenia, takich jak aparat.
Prośby o uprawnienia
Zminimalizuj liczbę uprawnień, o które prosi aplikacja. Ograniczenie dostępu do uprawnień wrażliwych zmniejsza ryzyko przypadkowego niewłaściwego wykorzystania tych uprawnień, zwiększa liczbę użytkowników i sprawia, że aplikacja jest mniej podatna na ataki. Jeśli uprawnienie nie jest wymagane do działania aplikacji, nie proś o nie. Zapoznaj się z przewodnikiem po ocenianiu, czy aplikacja musi deklarować uprawnienia.
Jeśli to możliwe, zaprojektuj aplikację w taki sposób, aby nie wymagała żadnych uprawnień. Zamiast na przykład prosić o dostęp do informacji o urządzeniu w celu utworzenia unikalnego identyfikatora, utwórz dla swojej aplikacji identyfikator UUID. (Więcej informacji znajdziesz w sekcji poświęconej danym użytkowników). Zamiast korzystać z pamięci zewnętrznej (co wymaga uprawnień), przechowuj dane w pamięci wewnętrznej.
Oprócz proszenia o uprawnienia aplikacja może używać elementu <permission> do ochrony komunikacji międzyprocesowej, która jest wrażliwa pod względem bezpieczeństwa i udostępniana innym aplikacjom, np. ContentProvider. Ogólnie zalecamy stosowanie innych mechanizmów kontroli dostępu niż uprawnienia potwierdzane przez użytkowników, ponieważ mogą one być dla nich niezrozumiałe. Na przykład możesz użyć poziomu ochrony podpisu w przypadku uprawnień do komunikacji międzyprocesowej między aplikacjami dostarczanymi przez jednego dewelopera.
Nie ujawniaj danych chronionych uprawnieniami. Dzieje się tak, gdy aplikacja udostępnia dane za pomocą komunikacji międzyprocesowej, a są one dostępne tylko dlatego, że aplikacja ma uprawnienia do uzyskiwania do nich dostępu. Klienci interfejsu IPC Twojej aplikacji mogą nie mieć tego samego uprawnienia dostępu do danych. Więcej informacji o częstotliwości występowania tego problemu i jego potencjalnych skutkach znajdziesz w artykule Permission Re-Delegation: Attacks and Defenses opublikowanym przez USENIX.
Definicje uprawnień
Określ najmniejszy zestaw uprawnień, który spełnia Twoje wymagania dotyczące bezpieczeństwa. Tworzenie nowych uprawnień jest stosunkowo rzadkie w przypadku większości aplikacji, ponieważ uprawnienia zdefiniowane przez system obejmują wiele sytuacji. W odpowiednich przypadkach sprawdzaj dostęp za pomocą istniejących uprawnień.
Jeśli potrzebujesz nowego uprawnienia, zastanów się, czy możesz wykonać zadanie przy użyciu poziomu ochrony podpisu. Uprawnienia związane z podpisem są niewidoczne dla użytkownika i umożliwiają dostęp tylko aplikacjom podpisanym przez tego samego dewelopera co aplikacja sprawdzająca uprawnienia.
Jeśli utworzenie nowego uprawnienia jest nadal konieczne, zadeklaruj je w pliku manifestu aplikacji za pomocą elementu <permission>. Aplikacje korzystające z nowego uprawnienia mogą się do niego odwoływać, dodając element <uses-permission> do plików manifestu. Uprawnienia możesz też dodawać dynamicznie za pomocą metody addPermission().
Jeśli utworzysz uprawnienie o poziomie ochrony dangerous, musisz wziąć pod uwagę kilka kwestii:
- Uprawnienie musi zawierać ciąg znaków, który w zwięzły sposób informuje użytkownika o decyzji dotyczącej bezpieczeństwa, jaką musi podjąć.
- Ciąg uprawnień musi być przetłumaczony na wiele różnych języków.
- Użytkownicy mogą nie zainstalować aplikacji, ponieważ uprawnienie jest niejasne lub postrzegane jako ryzykowne.
- Aplikacje mogą prosić o to uprawnienie, gdy twórca uprawnienia nie jest zainstalowany.
Każda z tych sytuacji stanowi dla Ciebie jako dewelopera poważne wyzwanie nietechniczne, a także wprowadza użytkowników w błąd. Dlatego odradzamy korzystanie z poziomu uprawnień „niebezpieczne”.
Sieci
Transakcje sieciowe są z natury ryzykowne pod względem bezpieczeństwa, ponieważ wiążą się z przesyłaniem danych, które mogą być prywatne dla użytkownika. Użytkownicy są coraz bardziej świadomi kwestii dotyczących prywatności na urządzeniach mobilnych, zwłaszcza gdy wykonują one transakcje sieciowe. Dlatego bardzo ważne jest, aby Twoja aplikacja wdrażała wszystkie sprawdzone metody zapewniające bezpieczeństwo danych użytkownika przez cały czas.
Sieć IP
Sieć na Androidzie nie różni się znacząco od innych środowisk Linux. Najważniejsze jest, aby w przypadku danych wrażliwych używać odpowiednich protokołów, takich jak HttpsURLConnection do zabezpieczania ruchu w internecie. Używaj protokołu HTTPS zamiast HTTP wszędzie tam, gdzie jest on obsługiwany na serwerze, ponieważ urządzenia mobilne często łączą się z sieciami, które nie są zabezpieczone, np. z publicznymi sieciami Wi-Fi.
Uwierzytelnioną, zaszyfrowaną komunikację na poziomie gniazda można łatwo wdrożyć za pomocą klasy SSLSocket. Biorąc pod uwagę częstotliwość, z jaką urządzenia z Androidem łączą się z niezabezpieczonymi sieciami bezprzewodowymi za pomocą Wi-Fi, zalecamy korzystanie z bezpiecznych sieci we wszystkich aplikacjach, które komunikują się przez sieć.
Niektóre aplikacje używają portów sieciowych localhost do obsługi poufnych procesów IPC. Nie używaj tego podejścia, ponieważ te interfejsy są dostępne dla innych aplikacji na urządzeniu. Zamiast tego użyj mechanizmu IPC na Androidzie, w którym możliwa jest autentykacja, np. Service. Powiązanie z nieokreślonym adresem IP INADDR_ANY jest gorsze niż użycie interfejsu zwrotnego, ponieważ umożliwia aplikacji odbieranie żądań z dowolnego adresu IP.
Nie ufaj danym pobranym z HTTP ani innych niezabezpieczonych protokołów. Obejmuje to weryfikację danych wejściowych w WebView i wszelkich odpowiedzi na intencje wysyłane przez HTTP.
Sieci telefoniczne
Protokół Short Message Service (SMS) został zaprojektowany głównie do komunikacji między użytkownikami i nie nadaje się do aplikacji, które chcą przesyłać dane. Ze względu na ograniczenia SMS-ów zalecamy używanie Komunikacji w chmurze Firebase (FCM) i sieci IP do wysyłania wiadomości z danymi z serwera WWW do aplikacji na urządzeniu użytkownika.
Pamiętaj, że SMS-y nie są szyfrowane ani silnie uwierzytelniane w sieci ani na urządzeniu. W szczególności każdy odbiorca SMS-a powinien się spodziewać, że złośliwy użytkownik mógł wysłać SMS-a do Twojej aplikacji. Nie polegaj na nieuwierzytelnionych danych SMS w przypadku wykonywania poleceń wymagających zachowania ostrożności. Pamiętaj też, że SMS-y mogą być fałszowane lub przechwytywane w sieci. Na urządzeniu z Androidem SMS-y są przesyłane jako intencje rozgłoszeniowe, więc mogą być odczytywane lub przechwytywane przez inne aplikacje, które mają uprawnienie READ_SMS.
Weryfikacja danych wejściowych
Niewystarczająca weryfikacja danych wejściowych to jeden z najczęstszych problemów z bezpieczeństwem dotyczących aplikacji niezależnie od platformy, na której działają. Android ma środki zaradcze na poziomie platformy, które zmniejszają narażenie aplikacji na problemy z weryfikacją danych wejściowych. Zalecamy korzystanie z tych funkcji, gdy jest to możliwe. Zalecamy też używanie języków bezpiecznych pod względem typów, aby zmniejszyć prawdopodobieństwo wystąpienia problemów z weryfikacją danych wejściowych.
Jeśli używasz kodu natywnego, wszelkie dane odczytywane z plików, odbierane przez sieć lub otrzymywane z IPC mogą powodować problemy z bezpieczeństwem. Najczęstsze problemy to przepełnienie bufora, użycie po zwolnieniu i błędy o 1. Android udostępnia szereg technologii, takich jak ASLR i zapobieganie wykonywaniu danych (DEP), które zmniejszają podatność na wykorzystanie tych błędów, ale nie rozwiązują podstawowego problemu. Możesz zapobiec tym lukom w zabezpieczeniach, ostrożnie obchodząc się ze wskaźnikami i zarządzając buforami.
Dynamiczne języki oparte na ciągach znaków, takie jak JavaScript i SQL, również mogą powodować problemy z weryfikacją danych wejściowych ze względu na znaki ucieczki i wstrzykiwanie skryptów.
Jeśli w zapytaniach przesyłanych do bazy danych SQL lub dostawcy treści używasz danych, problemem może być wstrzykiwanie kodu SQL. Najlepszą ochroną jest używanie zapytań parametryzowanych, o czym piszemy w sekcji dotyczącej dostawców treści. Ograniczenie uprawnień do odczytu lub zapisu może również zmniejszyć potencjalne szkody związane z wstrzyknięciem kodu SQL.
Jeśli nie możesz używać funkcji zabezpieczeń omówionych w tej sekcji, zadbaj o to, aby używać dobrze ustrukturyzowanych formatów danych, i sprawdź, czy dane są zgodne z oczekiwanym formatem. Blokowanie określonych znaków lub zastępowanie znaków może być skuteczną strategią, ale w praktyce te techniki są podatne na błędy, dlatego zalecamy ich unikanie, gdy jest to możliwe.
Dane użytkownika
Najlepszym sposobem na zapewnienie bezpieczeństwa danych użytkowników jest ograniczenie korzystania z interfejsów API, które mają dostęp do informacji poufnych lub danych osobowych. Jeśli masz dostęp do danych użytkownika, unikaj ich przechowywania i przesyłania, jeśli to możliwe. Zastanów się, czy logikę aplikacji można zaimplementować za pomocą funkcji skrótu lub nieodwracalnej formy danych. Na przykład aplikacja może używać skrótu adresu e-mail jako klucza podstawowego, aby uniknąć przesyłania lub przechowywania adresu e-mail. Zmniejsza to ryzyko przypadkowego ujawnienia danych, a także ryzyko, że atakujący będą próbowali wykorzystać luki w aplikacji.
Uwierzytelniaj użytkownika za każdym razem, gdy wymagany jest dostęp do danych prywatnych, i używaj nowoczesnych metod uwierzytelniania, takich jak klucze dostępu i Menedżer danych logowania. Jeśli Twoja aplikacja potrzebuje dostępu do danych osobowych, pamiętaj, że w niektórych jurysdykcjach może być wymagana polityka prywatności wyjaśniająca sposób wykorzystywania i przechowywania tych danych. Aby uprościć zachowanie zgodności, postępuj zgodnie ze sprawdzoną metodą zabezpieczania danych, która polega na minimalizowaniu dostępu do danych użytkownika.
Zastanów się też, czy Twoja aplikacja może nieumyślnie ujawniać dane osobowe innym podmiotom, np. komponentom reklamowym innych firm lub usługom innych firm używanym przez aplikację. Jeśli nie wiesz, dlaczego komponent lub usługa wymagają podania danych osobowych, nie podawaj ich. Ogólnie rzecz biorąc, ograniczenie dostępu aplikacji do danych osobowych zmniejsza potencjalne problemy w tym obszarze.
Jeśli aplikacja wymaga dostępu do danych wrażliwych, zastanów się, czy musisz przesyłać je na serwer, czy możesz wykonać operację na urządzeniu klienta. Rozważ uruchamianie kodu korzystającego z danych wrażliwych na urządzeniu klienta, aby uniknąć przesyłania danych użytkownika. Upewnij się też, że nie udostępniasz przypadkowo danych użytkownika innym aplikacjom na urządzeniu za pomocą zbyt liberalnego IPC, plików z uprawnieniami do zapisu dla wszystkich lub gniazd sieciowych. Zbyt liberalne IPC to szczególny przypadek wycieku danych chronionych uprawnieniami, omówiony w sekcji Prośby o uprawnienia.
Jeśli wymagany jest globalnie unikalny identyfikator (GUID), utwórz dużą, unikalną liczbę i ją zapisz. Nie używaj identyfikatorów telefonu, takich jak numer telefonu czy IMEI, które mogą być powiązane z danymi osobowymi. Ten temat jest bardziej szczegółowo omówiony na stronie sprawdzonych metod stosowania unikalnych identyfikatorów.
Zachowaj ostrożność podczas zapisywania danych w logach na urządzeniu. Na Androidzie logi są zasobem współdzielonym i są dostępne dla aplikacji z uprawnieniem READ_LOGS. Chociaż dane dziennika telefonu są tymczasowe i usuwane po ponownym uruchomieniu, nieodpowiednie rejestrowanie informacji o użytkowniku może spowodować przypadkowe ujawnienie danych użytkownika innym aplikacjom. Oprócz tego, że nie rejestrujesz informacji umożliwiających identyfikację, ogranicz użycie logów w aplikacjach produkcyjnych. Aby to ułatwić, użyj flag debugowania i niestandardowych klas Log z łatwo konfigurowanymi poziomami rejestrowania.
WebView
Ponieważ WebView korzysta z treści internetowych, które mogą zawierać HTML i JavaScript, nieprawidłowe użycie może powodować typowe problemy z bezpieczeństwem w internecie, takie jak cross-site scripting (wstrzykiwanie kodu JavaScript). Android zawiera szereg mechanizmów, które ograniczają zakres tych potencjalnych problemów, zmniejszając możliwości WebView do minimalnej funkcjonalności wymaganej przez aplikację.
Jeśli Twoja aplikacja nie używa bezpośrednio JavaScriptu w ramach elementu WebView, nie wywołuj funkcji setJavaScriptEnabled. Niektóre przykładowe kody używają tej metody. Jeśli w aplikacji produkcyjnej używasz przykładowego kodu, który jej używa, usuń to wywołanie metody, jeśli nie jest wymagane. Domyślnie WebView nie wykonuje kodu JavaScript, więc ataki typu XSS są niemożliwe.
Używaj addJavaScriptInterface() ze szczególną ostrożnością, ponieważ umożliwia to JavaScriptowi wywoływanie operacji, które są zwykle zarezerwowane dla aplikacji na Androida. Jeśli używasz tego interfejsu, udostępniaj go addJavaScriptInterface() tylko stronom internetowym, z których wszystkie dane wejściowe są wiarygodne. Jeśli zezwolisz na niezaufane dane wejściowe, niezaufany kod JavaScript może wywoływać w aplikacji metody Androida. Ogólnie zalecamy udostępnianie addJavaScriptInterface() tylko kodowi JavaScript zawartemu w pliku APK aplikacji.
Jeśli Twoja aplikacja uzyskuje dostęp do danych wrażliwych za pomocą WebView, rozważ użycie metody clearCache(), aby usunąć wszystkie pliki przechowywane lokalnie. Możesz też używać nagłówków po stronie serwera, np. no-store, aby wskazać, że aplikacja nie powinna buforować określonych treści.
Urządzenia z platformami starszymi niż Android 4.4 (poziom interfejsu API 19) używają wersji webkit, która ma wiele problemów z bezpieczeństwem. Jeśli aplikacja działa na tych urządzeniach, musi potwierdzić, że obiekty WebView wyświetlają tylko zaufane treści. Aby mieć pewność, że Twoja aplikacja nie jest narażona na potencjalne luki w protokole SSL, użyj obiektu zabezpieczeń Provider, który można aktualizować, zgodnie z opisem w artykule Aktualizowanie dostawcy zabezpieczeń w celu ochrony przed wykorzystywaniem luk w protokole SSL. Jeśli Twoja aplikacja musi renderować treści z otwartej sieci, rozważ udostępnienie własnego renderera, aby móc go aktualizować za pomocą najnowszych poprawek zabezpieczeń.
Prośby o dane logowania
Żądania danych logowania są wektorem ataku. Oto kilka wskazówek, które pomogą Ci zwiększyć bezpieczeństwo żądań danych logowania w aplikacjach na Androida.
Minimalizowanie ekspozycji danych logowania
- Unikaj zbędnych próśb o dane logowania. Aby ataki phishingowe były bardziej widoczne i mniej skuteczne, ogranicz częstotliwość proszenia użytkowników o podanie danych logowania. Zamiast tego użyj tokena autoryzacji i odśwież go. Proś tylko o minimalną ilość informacji o danych logowania niezbędnych do uwierzytelniania i autoryzacji.
- Bezpieczne przechowywanie danych logowania Użyj Credential Manager, aby włączyć uwierzytelnianie bez hasła za pomocą kluczy dostępu lub zaimplementować logowanie federacyjne za pomocą schematów takich jak Zaloguj się przez Google. Jeśli musisz używać tradycyjnego uwierzytelniania za pomocą hasła, nie przechowuj identyfikatorów użytkowników ani haseł na urządzeniu. Zamiast tego przeprowadź wstępne uwierzytelnianie za pomocą nazwy użytkownika i hasła podanych przez użytkownika, a następnie użyj krótkotrwałego tokena autoryzacji specyficznego dla usługi.
- Ograniczanie zakresu uprawnień Nie proś o szerokie uprawnienia do zadania, które wymaga tylko węższego zakresu.
- Ogranicz tokeny dostępu. Używaj operacji i wywołań interfejsu API związanych z krótkotrwałymi tokenami.
- Ograniczanie liczby uwierzytelnień Szybkie, kolejne żądania uwierzytelnienia lub autoryzacji mogą być oznaką ataku siłowego. Ogranicz te częstotliwości do rozsądnego poziomu, ale jednocześnie zapewnij funkcjonalność i wygodę użytkowania aplikacji.
Stosowanie bezpiecznego uwierzytelniania
- Wdrażanie kluczy dostępu Włącz klucze dostępu jako bezpieczniejszą i wygodniejszą alternatywę dla haseł.
- Dodaj biometrię. Umożliwiać korzystanie z uwierzytelniania biometrycznego, np. za pomocą odcisku palca lub rozpoznawania twarzy, w celu zwiększenia bezpieczeństwa.
- Używaj sfederowanych dostawców tożsamości. Credential Manager obsługuje dostawców uwierzytelniania federacyjnego, takich jak Zaloguj się przez Google.
- Szyfruj komunikację Używaj protokołu HTTPS i podobnych technologii, aby chronić dane przesyłane przez aplikację w sieci.
Stosuj bezpieczne metody zarządzania kontem
- Połącz się z usługami dostępnymi dla wielu aplikacji za pomocą
AccountManager. Użyj klasyAccountManager, aby wywołać usługę działającą w chmurze, i nie przechowuj haseł na urządzeniu. - Po użyciu
AccountManagerdo pobraniaAccountużyjCREATORprzed przekazaniem jakichkolwiek danych logowania, aby nie przekazać ich przypadkowo do niewłaściwej aplikacji. - Jeśli dane logowania są używane tylko przez aplikacje, które tworzysz, możesz zweryfikować aplikację uzyskującą dostęp do
AccountManagerza pomocącheckSignatures. Jeśli z danych logowania korzysta tylko jedna aplikacja, możesz użyćKeyStoredo ich przechowywania.
Zachowaj ostrożność
- Aktualizuj kod. Pamiętaj, aby zaktualizować kod źródłowy, w tym wszystkie biblioteki i zależności zewnętrzne, aby chronić się przed najnowszymi lukami w zabezpieczeniach.
- Monitoruj podejrzaną aktywność. Wyszukuj potencjalne nadużycia, np. wzorce nadużywania autoryzacji.
- Sprawdź kod. Regularnie sprawdzaj bazę kodu pod kątem potencjalnych problemów z żądaniami danych logowania.
Zarządzanie kluczami interfejsu API
Klucze interfejsu API są kluczowym elementem wielu aplikacji na Androida, ponieważ umożliwiają im dostęp do usług zewnętrznych i wykonywanie podstawowych funkcji, takich jak łączenie się z usługami map, uwierzytelnianie i usługi pogodowe. Ujawnienie tych kluczy wrażliwych może jednak mieć poważne konsekwencje, w tym naruszenie bezpieczeństwa danych, nieautoryzowany dostęp i straty finansowe. Aby zapobiec takim sytuacjom, deweloperzy powinni wdrażać bezpieczne strategie obsługi kluczy interfejsu API w całym procesie tworzenia aplikacji.
Aby chronić usługi przed niewłaściwym użyciem, klucze interfejsu API muszą być starannie zabezpieczone. Aby zabezpieczyć połączenie między aplikacją a usługą, która używa klucza interfejsu API, musisz zabezpieczyć dostęp do interfejsu API. Gdy aplikacja jest kompilowana, a jej kod źródłowy zawiera klucze interfejsu API, atakujący może zdekompilować aplikację i znaleźć te zasoby.
Ta sekcja jest przeznaczona dla 2 grup programistów na Androida: tych, którzy pracują z zespołami ds. infrastruktury nad potokiem trybu ciągłego dostarczania, oraz tych, którzy wdrażają samodzielne aplikacje w Sklepie Play. W tej sekcji znajdziesz sprawdzone metody postępowania z kluczami interfejsu API, dzięki którym Twoja aplikacja będzie mogła bezpiecznie komunikować się z usługami.
Generowanie i przechowywanie
Deweloperzy powinni traktować przechowywanie kluczy interfejsu API jako kluczowy element ochrony danych i prywatności użytkowników, stosując podejście oparte na wielowarstwowej ochronie.
Bezpieczne przechowywanie kluczy
Aby zapewnić optymalne bezpieczeństwo zarządzania kluczami, używaj magazynu kluczy Androida i szyfruj przechowywane klucze za pomocą niezawodnego narzędzia, takiego jak Tink Java.
Wykluczenie kontroli źródła
Nigdy nie zapisuj kluczy interfejsu API w repozytorium kodu źródłowego. Dodawanie kluczy interfejsu API do kodu źródłowego wiąże się z ryzykiem ujawnienia ich w publicznych repozytoriach, udostępnionych przykładach kodu i przypadkowo udostępnionych plikach. Zamiast tego używaj wtyczek Gradle, takich jak secrets-gradle-plugin, aby pracować z kluczami interfejsu API w projekcie.
Klucze specyficzne dla środowiska
Jeśli to możliwe, używaj osobnych kluczy interfejsu API w środowiskach programistycznym, testowym i produkcyjnym. Używaj kluczy specyficznych dla środowiska, aby odizolować poszczególne środowiska, zmniejszając ryzyko ujawnienia danych produkcyjnych i umożliwiając wyłączenie naruszonych kluczy bez wpływu na środowisko produkcyjne.
Kontrola użytkowania i dostępu
Stosowanie bezpiecznych metod korzystania z kluczy interfejsu API jest niezbędne do ochrony interfejsu API i użytkowników. Aby zapewnić optymalne bezpieczeństwo, przygotuj klucze w ten sposób:
- Generuj unikalne klucze dla każdej aplikacji: używaj oddzielnych kluczy interfejsu API dla każdej aplikacji, aby ułatwić identyfikowanie i izolowanie naruszonego dostępu.
- Wdróż ograniczenia IP: jeśli to możliwe, ogranicz użycie klucza interfejsu API do określonych adresów IP lub zakresów adresów IP.
- Ograniczanie użycia klucza w aplikacjach mobilnych: ogranicz użycie klucza interfejsu API do określonych aplikacji mobilnych, łącząc je z kluczem lub używając certyfikatów aplikacji.
- Rejestrowanie i monitorowanie podejrzanej aktywności: wdróż mechanizmy rejestrowania i monitorowania wykorzystania interfejsu API, aby wykrywać podejrzaną aktywność i zapobiegać potencjalnym nadużyciom.
Uwaga: usługa powinna udostępniać funkcje ograniczające klucze do konkretnego pakietu lub platformy. Na przykład interfejs Google Maps API ogranicza dostęp do klucza według nazwy pakietu i klucza podpisywania.
OAuth 2.0 to platforma autoryzacji dostępu do zasobów. Określa standardy interakcji między klientami a serwerami i umożliwia bezpieczną autoryzację. Możesz użyć OAuth 2.0, aby ograniczyć użycie klucza interfejsu API do określonych klientów i zdefiniować zakres dostępu, tak aby każdy klucz interfejsu API miał tylko minimalny poziom dostępu wymagany do zamierzonego celu.
Rotacja i wygaśnięcie kluczy
Aby zmniejszyć ryzyko nieautoryzowanego dostępu przez niewykryte luki w interfejsie API, ważne jest regularne zmienianie kluczy interfejsu API. Standard ISO 27001 określa ramy zgodności dotyczące częstotliwości rotacji kluczy. W większości przypadków wystarczy okres rotacji kluczy wynoszący od 90 dni do 6 miesięcy. Wdrożenie solidnego systemu zarządzania kluczami może pomóc w usprawnieniu tych procesów, zwiększając efektywność rotacji kluczy i spełniając potrzeby związane z ich wygasaniem.
Ogólne sprawdzone metody
- Używaj protokołu SSL/HTTPS: zawsze używaj komunikacji HTTPS, aby szyfrować żądania interfejsu API.
- Przypinanie certyfikatów: aby zapewnić dodatkową warstwę zabezpieczeń, możesz wdrożyć przypinanie certyfikatów, aby sprawdzać, które certyfikaty są uznawane za prawidłowe.
- Sprawdzaj i oczyszczaj dane wejściowe użytkownika: sprawdzaj i oczyszczaj dane wejściowe użytkownika, aby zapobiegać atakom typu injection, które mogą ujawnić klucze interfejsu API.
- Stosuj sprawdzone metody dotyczące bezpieczeństwa: w procesie tworzenia aplikacji wdrażaj ogólne sprawdzone metody dotyczące bezpieczeństwa, w tym techniki bezpiecznego kodowania, sprawdzanie kodu i skanowanie pod kątem luk w zabezpieczeniach.
- Bądź na bieżąco: śledź najnowsze zagrożenia dla bezpieczeństwa i sprawdzone metody zarządzania kluczami interfejsu API.
- Aktualne pakiety SDK: upewnij się, że pakiety SDK i biblioteki są zaktualizowane do najnowszej wersji.
Kryptografia
Android zapewnia nie tylko izolację danych, obsługę pełnego szyfrowania systemu plików i bezpieczne kanały komunikacji, ale też szeroką gamę algorytmów do ochrony danych za pomocą kryptografii.
Sprawdź, z których dostawców zabezpieczeń Java Cryptography Architecture (JCA) korzysta Twoje oprogramowanie. Staraj się korzystać z najwyższego poziomu istniejącej implementacji platformy, która obsługuje Twój przypadek użycia. W odpowiednich przypadkach używaj dostawców podanych przez Google w określonej przez nią kolejności.
Jeśli musisz bezpiecznie pobrać plik ze znanego miejsca w sieci, wystarczy prosty identyfikator URI HTTPS, który nie wymaga znajomości kryptografii. Jeśli potrzebujesz bezpiecznego tunelu, rozważ użycie HttpsURLConnection lub SSLSocket zamiast tworzyć własny protokół. Jeśli używasz SSLSocket, pamiętaj, że nie przeprowadza ona weryfikacji nazwy hosta. Zobacz ostrzeżenia dotyczące bezpośredniego używania SSLSocket.
Jeśli okaże się, że musisz wdrożyć własny protokół, nie implementuj własnych algorytmów kryptograficznych. Używaj istniejących algorytmów kryptograficznych, takich jak implementacje AES i RSA udostępnione w klasie Cipher.
Dodatkowo postępuj zgodnie z tymi sprawdzonymi metodami:
- używać 256-bitowego algorytmu AES do celów komercyjnych; (Jeśli jest niedostępne, użyj 128-bitowego szyfrowania AES).
- W przypadku kryptografii opartej na krzywych eliptycznych (EC) używaj kluczy publicznych o rozmiarze 224 lub 256 bitów.
- Dowiedz się, kiedy używać trybów blokowych CBC, CTR lub GCM.
- Unikaj ponownego użycia wektora inicjującego lub licznika w trybie CTR. Upewnij się, że są one kryptograficznie losowe.
- Jeśli używasz szyfrowania, zaimplementuj integralność za pomocą trybu CBC lub CTR z jedną z tych funkcji:
- HMAC-SHA1
- HMAC-SHA-256
- HMAC-SHA-512
- Tryb GCM
Do inicjowania kluczy kryptograficznych generowanych przez KeyGenerator używaj bezpiecznego generatora liczb losowych SecureRandom. Użycie klucza, który nie został wygenerowany za pomocą bezpiecznego generatora liczb losowych, znacznie osłabia siłę algorytmu i może umożliwić ataki offline.
Jeśli musisz przechowywać klucz do wielokrotnego użycia, skorzystaj z mechanizmu, takiego jak KeyStore, który zapewnia długoterminowe przechowywanie i pobieranie kluczy kryptograficznych.
Komunikacja między procesami
Niektóre aplikacje próbują wdrożyć IPC za pomocą tradycyjnych technik Linuksa, takich jak gniazda sieciowe i pliki współdzielone. Zamiast tego zalecamy korzystanie z funkcji systemu Android do komunikacji międzyprocesowej, takich jak Intent, Binder lub Messenger z Service i BroadcastReceiver. Mechanizmy IPC Androida umożliwiają weryfikację tożsamości aplikacji łączącej się z IPC i ustawianie zasad bezpieczeństwa dla każdego mechanizmu IPC.
Wiele elementów zabezpieczeń jest wspólnych dla mechanizmów IPC. Jeśli mechanizm IPC nie jest przeznaczony do użytku przez inne aplikacje, ustaw atrybut android:exported na false w elemencie manifestu komponentu, np. w elemencie <service>. Jest to przydatne w przypadku aplikacji, które składają się z wielu procesów w ramach tego samego identyfikatora UID, lub jeśli w późniejszej fazie tworzenia aplikacji zdecydujesz, że nie chcesz udostępniać funkcji jako IPC, ale nie chcesz też przepisywać kodu.
Jeśli komunikacja międzyprocesowa jest dostępna dla innych aplikacji, możesz zastosować politykę bezpieczeństwa, używając elementu <permission>. Jeśli komunikacja IPC odbywa się między Twoimi aplikacjami podpisanymi tym samym kluczem, użyj uprawnienia signature-level w android:protectionLevel.
Intencje
W przypadku aktywności i odbiorników intencji preferowanym mechanizmem asynchronicznego IPC na Androidzie są intencje. W zależności od wymagań aplikacji możesz użyć sendBroadcast, sendOrderedBroadcast lub wyraźnego zamiaru dotyczącego konkretnego komponentu aplikacji. Ze względów bezpieczeństwa preferowane są wyraźne intencje.
Pamiętaj, że uporządkowane transmisje mogą być konsumowane przez odbiorcę, więc mogą nie być dostarczane do wszystkich aplikacji. Jeśli wysyłasz intencję, która musi zostać dostarczona do konkretnego odbiorcy, musisz użyć intencji bezpośredniej, która deklaruje odbiorcę według nazwy.
Nadawcy intencji mogą sprawdzić, czy odbiorca ma uprawnienia, określając w wywołaniu metody uprawnienie inne niż null. Tylko aplikacje z tym uprawnieniem otrzymają intencję. Jeśli dane w intencji rozgłaszania mogą być poufne, rozważ zastosowanie uprawnień, aby złośliwe aplikacje nie mogły rejestrować się w celu otrzymywania tych wiadomości bez odpowiednich uprawnień. W takich przypadkach możesz też wywołać odbiornik bezpośrednio, zamiast wysyłać transmisję.
Usługi
Service jest często używany do udostępniania funkcji innym aplikacjom. Każda klasa usługi musi mieć odpowiednią deklarację <service> w pliku manifestu.
Domyślnie usługi nie są eksportowane i nie mogą być wywoływane przez żadną inną aplikację. Jeśli jednak dodasz do deklaracji usługi jakiekolwiek filtry intencji, zostaną one wyeksportowane domyślnie. Najlepiej jest jawnie zadeklarować atrybut android:exported, aby mieć pewność, że działa on zgodnie z Twoimi oczekiwaniami. Usługi można też chronić za pomocą atrybutu android:permission. W ten sposób inne aplikacje muszą zadeklarować odpowiedni element <uses-permission> w swoim pliku manifestu, aby móc uruchamiać, zatrzymywać lub wiązać usługę.
Usługa może chronić poszczególne wywołania IPC, które są do niej kierowane, za pomocą uprawnień. W tym celu przed wykonaniem implementacji wywołaj funkcję checkCallingPermission(). Zalecamy używanie deklaratywnych uprawnień w pliku manifestu, ponieważ są one mniej podatne na przeoczenia.
Interfejsy Binder i Messenger
Używanie Binder lub Messenger to preferowany mechanizm komunikacji międzyprocesowej w stylu RPC na Androidzie. Udostępniają one dobrze zdefiniowane interfejsy, które w razie potrzeby umożliwiają wzajemne uwierzytelnianie punktów końcowych.
Zalecamy projektowanie interfejsów aplikacji w taki sposób, aby nie wymagały one sprawdzania uprawnień specyficznych dla interfejsu. Obiekty Binder i Messenger nie są zadeklarowane w manifeście aplikacji, dlatego nie możesz bezpośrednio zastosować do nich uprawnień deklaratywnych. Zwykle dziedziczą uprawnienia zadeklarowane w pliku manifestu aplikacji dla Service lub Activity, w których są zaimplementowane. Jeśli tworzysz interfejs, który wymaga uwierzytelniania lub kontroli dostępu, musisz wyraźnie dodać te elementy sterujące jako kod w interfejsie Binder lub Messenger.
Jeśli udostępniasz interfejs, który wymaga kontroli dostępu, użyj checkCallingPermission(), aby sprawdzić, czy element wywołujący ma wymagane uprawnienia. Jest to szczególnie ważne przed uzyskaniem dostępu do usługi w imieniu rozmówcy, ponieważ tożsamość aplikacji jest przekazywana do innych interfejsów.
Jeśli wywołujesz interfejs udostępniany przez Service, wywołanie bindService() może się nie udać, jeśli nie masz uprawnień dostępu do danej usługi. Jeśli chcesz zezwolić procesowi zewnętrznemu na interakcję z aplikacją, ale nie ma on do tego odpowiednich uprawnień, możesz użyć metody clearCallingIdentity(). Ta metoda wykonuje połączenie z interfejsem aplikacji tak, jakby to ona sama wykonywała połączenie, a nie dzwoniący z zewnątrz. Uprawnienia wywołującego możesz później przywrócić za pomocą metody restoreCallingIdentity().
Więcej informacji o wykonywaniu IPC za pomocą usługi znajdziesz w artykule Usługi powiązane.
Odbiorniki
BroadcastReceiver obsługuje żądania asynchroniczne inicjowane przez Intent.
Domyślnie odbiorniki są eksportowane i mogą być wywoływane przez dowolną inną aplikację.
Jeśli Twój BroadcastReceiver jest przeznaczony do używania przez inne aplikacje, możesz zastosować uprawnienia zabezpieczeń do odbiorców za pomocą elementu <receiver> w pliku manifestu aplikacji. Uniemożliwia to aplikacjom bez odpowiednich uprawnień wysyłanie intencji do komponentu BroadcastReceiver.
Bezpieczeństwo w przypadku dynamicznie wczytywanego kodu
Zdecydowanie odradzamy wczytywanie kodu spoza pakietu APK aplikacji. Znacznie zwiększa to prawdopodobieństwo naruszenia bezpieczeństwa aplikacji z powodu wstrzyknięcia lub zmodyfikowania kodu. Dodatkowo komplikuje zarządzanie wersjami i testowanie aplikacji, a także może uniemożliwiać weryfikację jej działania, dlatego w niektórych środowiskach może być zabronione.
Jeśli aplikacja dynamicznie wczytuje kod, najważniejsze jest to, że wczytany dynamicznie kod działa z tymi samymi uprawnieniami zabezpieczeń co plik APK aplikacji. Użytkownik podejmuje decyzję o zainstalowaniu Twojej aplikacji na podstawie Twojej tożsamości i oczekuje, że dostarczysz cały kod uruchamiany w aplikacji, w tym kod ładowany dynamicznie.
Wiele aplikacji próbuje wczytać kod z niebezpiecznych lokalizacji, np. pobrany z sieci za pomocą niezaszyfrowanych protokołów lub z lokalizacji z uprawnieniami do zapisu dla wszystkich użytkowników, takich jak pamięć zewnętrzna. Te lokalizacje mogą umożliwić osobie w sieci modyfikowanie treści w trakcie przesyłania lub innej aplikacji na urządzeniu użytkownika w celu modyfikowania treści na urządzeniu. Z drugiej strony modułów zawartych bezpośrednio w pliku APK nie można modyfikować za pomocą innych aplikacji. Dotyczy to zarówno bibliotek natywnych, jak i klas wczytywanych za pomocą funkcji DexClassLoader.
Bezpieczeństwo na maszynie wirtualnej
Dalvik to środowisko wykonawcze maszyny wirtualnej (VM) Androida. Dalvik został stworzony specjalnie dla Androida, ale wiele problemów dotyczących bezpiecznego kodu w innych maszynach wirtualnych dotyczy też Androida. Zazwyczaj nie musisz się martwić problemami z bezpieczeństwem związanymi z maszyną wirtualną. Aplikacja działa w bezpiecznym środowisku piaskownicy, więc inne procesy w systemie nie mogą uzyskać dostępu do Twojego kodu ani danych prywatnych.
Jeśli chcesz dowiedzieć się więcej o bezpieczeństwie maszyn wirtualnych, zapoznaj się z dostępną literaturą na ten temat. Dwa z najpopularniejszych materiałów to:
Ten dokument koncentruje się na obszarach, które są specyficzne dla Androida lub różnią się od innych środowisk maszyn wirtualnych. Deweloperzy, którzy mają doświadczenie w programowaniu maszyn wirtualnych w innych środowiskach, mogą napotkać 2 główne problemy, które różnią się od pisania aplikacji na Androida:
- Niektóre maszyny wirtualne, takie jak JVM czy środowisko wykonawcze .NET, stanowią granicę bezpieczeństwa, izolującą kod od możliwości systemu operacyjnego. W Androidzie maszyna wirtualna Dalvik nie jest granicą bezpieczeństwa – piaskownica aplikacji jest zaimplementowana na poziomie systemu operacyjnego, więc Dalvik może współdziałać z kodem natywnym w tej samej aplikacji bez żadnych ograniczeń bezpieczeństwa.
- Ze względu na ograniczoną ilość miejsca na urządzeniach mobilnych programiści często chcą tworzyć aplikacje modułowe i korzystać z dynamicznego wczytywania klas. Podczas tego procesu weź pod uwagę zarówno źródło, z którego pobierasz logikę aplikacji, jak i miejsce, w którym przechowujesz ją lokalnie. Nie używaj dynamicznego wczytywania klas ze źródeł, które nie są zweryfikowane, np. z niezabezpieczonych źródeł sieciowych lub pamięci zewnętrznej, ponieważ kod może zostać zmodyfikowany w taki sposób, aby zawierał szkodliwe działania.
Bezpieczeństwo w kodzie natywnym
Ogólnie zalecamy używanie pakietu Android SDK do tworzenia aplikacji, a nie kodu natywnego z Android NDK. Aplikacje napisane w kodzie natywnym są bardziej złożone, mniej przenośne i częściej zawierają typowe błędy uszkodzenia pamięci, takie jak przepełnienia bufora.
Android jest oparty na jądrze systemu Linux, więc znajomość sprawdzonych metod dotyczących bezpieczeństwa podczas tworzenia aplikacji na Linuxa jest szczególnie przydatna, jeśli używasz kodu natywnego. Praktyki dotyczące bezpieczeństwa systemu Linux wykraczają poza zakres tego dokumentu, ale jednym z najpopularniejszych źródeł informacji jest Secure Programming HOWTO - Creating Secure Software (w języku angielskim).
Ważną różnicą między Androidem a większością środowisk Linux jest piaskownica aplikacji. Na Androidzie wszystkie aplikacje działają w piaskownicy aplikacji, w tym te napisane w kodzie natywnym. Deweloperzy znający system Linux mogą sobie wyobrazić, że każda aplikacja otrzymuje unikalny identyfikator użytkownika (UID) z bardzo ograniczonymi uprawnieniami. Więcej informacji znajdziesz w omówieniu bezpieczeństwa Androida. Z uprawnieniami aplikacji warto się zapoznać nawet wtedy, gdy używasz kodu natywnego.