Na proces hakowania urządzeń mobilnych składa się wiele działań, w tym m.in. analiza pakietów wychodzących aplikacji. Aby uniemożliwić taką analizę, zaprojektowano SSL pinning, którym zajmiemy się w tym artykule.
SSL pinning, znane również jako pinning certyfikatów, odgrywa istotną rolę w ochronie połączeń SSL/TLS między aplikacją mobilną a serwerem. Poprzez przypięcie określonych certyfikatów lub kluczy publicznych aplikacje zapewniają, że komunikacja odbywa się wyłącznie z autoryzowanymi serwerami. Każdą technologię można jednak przełamać. Techniki bypassu SSL pinningu, czyli metody umożliwiające obejście tego zabezpieczenia, są nie tylko narzędziem w arsenale atakujących, ale także kluczowym elementem w testach penetracyjnych i analizie bezpieczeństwa aplikacji. Zrozumienie tych metod, od modyfikacji kodu aplikacji po zaawansowane techniki dynamicznej analizy, jest niezbędne dla każdego specjalisty IT zajmującego się bezpieczeństwem aplikacji mobilnych.
W poprzednich artykułach poświęconych testowaniu bezpieczeństwa technologii mobilnych („IT Professional” 10/2023, s. 17) omówiliśmy kwestie związane z inżynierią wsteczną, modyfikowaniem, kompilowaniem i dekompilowaniem aplikacji oraz przechwytywaniem ruchu. W tym tekście przyjrzymy się temu, w jaki sposób działa SSL pinning, oraz przybliżymy sposoby przypinania certyfikatów.
Metody ochrony
SSL pinning to mechanizm bezpieczeństwa stosowany w aplikacjach mobilnych i internetowych, mający na celu zwiększenie ochrony połączeń klient–serwer realizowanych przez protokoły SSL/TLS takie jak HTTPS. Działanie SSL można w skrócie przedstawić w następujących punktach:
- Maszyna kliencka wysyła żądanie połączenia do serwera, serwer nasłuchuje żądania.
- Serwer wysyła odpowiedź zawierającą klucz publiczny i certyfikat.
- Klient sprawdza certyfikat i wysyła zaszyfrowany klucz do serwera.
- Serwer odszyfrowuje klucz i wysyła zaszyfrowane dane z powrotem do komputera klienckiego.
- Klient odbiera i odszyfrowuje dane.
Przypinanie umożliwia powiązanie hosta z jego oczekiwaną tożsamością kryptograficzną, co jest istotne dla zabezpieczenia przed atakami typu man-in-the-middle (MITM) oraz zapobiegania przechwytywaniu ruchu przez narzędzia takie jak Burp Suite. Tożsamość kryptograficzna to plik, który może udowodnić autentyczność serwera lub hosta za pomocą kryptografii. Gdy jest ona znana lub widoczna dla hosta, zostaje z nim skojarzona lub do niego przypięta. W przypadku gdy więcej niż jedna tożsamość jest akceptowalna, host przechowuje je jako zestaw (pinset), z którego aplikacja wybiera odpowiednią podczas nawiązywania połączenia.
Standardowe połączenie SSL pozwala klientowi na ustanowienie szyfrowanego połączenia z dowolną tożsamością pasującą do hosta, jednak przypinanie SSL informuje klienta o określonej tożsamości, która ma zostać zaakceptowana. Technika ta opiera się na przypisaniu określonego certyfikatu lub klucza publicznego do hosta, zamiast polegać na dowolnym certyfikacie podpisanym przez zaufane urzędy certyfikacji (ang. Certificate Authorities, CA). Aplikacja jest skonfigurowana tak, aby odrzucała wszystkie certyfikaty lub klucze publiczne z wyjątkiem tych predefiniowanych, co oznacza, że za każdym razem, gdy łączy się z serwerem, porównuje jego certyfikat z przypiętymi certyfikatami lub kluczami publicznymi. Tylko jeśli są one zgodne, aplikacja ufa serwerowi i nawiązuje połączenie. Zazwyczaj certyfikat lub klucz publiczny usługi dodaje się w czasie programowania. Oznacza to, że aplikacja mobilna powinna zawierać certyfikat cyfrowy lub klucz publiczny w pakiecie aplikacji. Jest to preferowana metoda, ponieważ atakujący nie może zanieczyścić pinu.
Certyfikaty SSL, które są używane w tym procesie, mają parę kluczy: publiczny i prywatny. Sam certyfikat to plik, który zawiera informacje o serwerze będącym właścicielem certyfikatu. Struktura certyfikatu wykorzystuje standardy X.509. Jest on zdefiniowany przez sektor standaryzacji Międzynarodowego Związku Telekomunikacyjnego (ITU). Urząd certyfikujący może wydać certyfikat lub może on być samopodpisany. Przypinanie certyfikatów jest potrzebne, ponieważ aplikacja, próbując nawiązać połączenie z serwerem, musi wiedzieć, którym certyfikatom ufać. W przeciwnym razie polega na certyfikatach dostarczanych przez iOS Trust Store lub Android CA. Atakujący może jednak wygenerować samodzielnie podpisany certyfikat i dołączyć go do iOS/Android Trust Store lub zhakować certyfikat głównego urzędu certyfikacji. Pozwala to atakującemu na przeprowadzenie ataku typu MITM i przechwycenie przesyłanych danych do i z aplikacji. Ograniczenie zestawu zaufanych certyfikatów poprzez przypinanie uniemożliwia atakującym analizowanie funkcjonalności aplikacji i sposobu, w jaki komunikuje się ona z serwerem.
Opcje pinowania
W standardowym uzgadnianiu TLS, gdy klient łączy się z serwerem, serwer przedstawia klientowi swój cyfrowy certyfikat, a klient weryfikuje jego autentyczność. Jeśli certyfikat został podpisany przez zaufany urząd certyfikacji i nie utracił ważności, połączenie jest kontynuowane. Przypinanie przenosi ten proces na wyższy poziom, polegając na wstępnie skonfigurowanej liście zaufanych kluczy publicznych lub certyfikatów. W przypadku niepowodzenia weryfikacji pinu certyfikatu połączenie nie zostanie nawiązane, a ruch wysłany.
Aplikacje mogą używać różnych metod do weryfikacji pinu certyfikatu, ale logika jest taka sama. Jeśli pin nie zostanie zweryfikowany, nie wygeneruje się żadne połączenie i nie dojdzie do wysłania ruchu. Poniżej przedstawiamy dwie opcje weryfikacji:
- przypisywanie certyfikatów (ang. certificate pinning) – pierwszą z możliwości jest przechowywanie w aplikacj certyfikatu, który jest powiązany z określonym serwerem. Podczas nawiązywania połączenia aplikacja porównuje certyfikat otrzymany od serwera z tym osadzonym w kodzie. Jeśli są one zgodne, połączenie zostaje nawiązane; w przeciwnym razie jest odrzucane. W tej metodzie pin jest obliczany na podstawie certyfikatu, który musi być zaktualizowany po wygaśnięciu certyfikatu serwera;
- przypinanie klucza publicznego (ang. public key pinning) – to bardziej elastyczna metoda, w której aplikacja przechowuje klucz publiczny powiązany z certyfikatem zamiast samego certyfikatu. Klucz publiczny certyfikatu można pobrać i umieścić w kodzie jako ciąg znaków. Podczas wykonywania aplikacja porównuje klucz publiczny otrzymany z serwera z kluczem publicznym zakodowanym w aplikacji. Jeśli klucz publiczny nie jest zgodny, połączenie zostaje odrzucone. Metoda ta jest trudniejsza do wdrożenia, ale oferuje większą trwałość, ponieważ nie wymaga zmiany klucza przy wygaśnięciu certyfikatu.
Weryfikacja SSL
Podczas walidacji certyfikatu zazwyczaj weryfikowane jest całe drzewo urzędów certyfikacji (ang. Certificate Authority Tree). Jest to hierarchiczna struktura, która opisuje zależności pomiędzy różnymi urzędami oraz certyfikatami, które wydają. W tej hierarchii głównym elementem jest tzw. root CA (główny urząd certyfikacji), który jest na szczycie drzewa i ma certyfikat podpisany przez samego siebie. Root CA ma pełne zaufanie w całym systemie i jego certyfikat stanowi podstawę zaufania dla wszystkich innych certyfikatów w danym drzewie. W uproszczeniu strukturę drzewa urzędów certyfikacji można przedstawić następująco:
- Root CA (główny urząd certyfikacji) – najważniejszy element hierarchii, mający certyfikat podpisany przez samego siebie, co oznacza, że nie jest podpisany przez żaden wyższy urząd. Root CA jest używany do podpisywania certyfikatów intermediate CA oraz innych certyfikatów końcowych.
- Intermediate CA (pośredni urząd certyfikacji) – znajduje się pod Root CA w hierarchii. Intermediate CA jest podpisywany przez Root CA i może samodzielnie wydawać certyfikaty końcowe lub przekazywać tę możliwość kolejnym pośrednim CA. Jest toRoot CA od bezpośredniego wystawiania certyfikatów użytkownikom końcowym, co zwiększa bezpieczeństwo.
- Leaf CA (certyfikaty końcowe) – to certyfikaty wydawane użytkownikom końcowym, serwerom, aplikacjom lub urządzeniom. Znajdują się na najniższym poziomie hierarchii i są podpisywane przez pośrednie urzędy certyfikacji. Leaf certificates są używane do zabezpieczania komunikacji przez protokoły takie jak SSL/TLS.
Gdy klient, taki jak przeglądarka internetowa, nawiązuje połączenie z serwerem, serwer przedstawia certyfikat, który został wydany przez intermediate CA. Następnie klient weryfikuje go, sprawdzając, czy jest on podpisany przez intermediate CA, a ten z kolei został podpisany przez root CA. Jeśli wszystkie certyfikaty są zgodne, uznaje się, że połączenie jest bezpieczne, ponieważ ścieżka weryfikacji certyfikatu prowadzi do zaufanego root CA.
Drzewa urzędów certyfikacji to kluczowy element infrastruktury klucza publicznego (PKI) i są wykorzystywane do zapewnienia zaufania w systemach SSL/TLS, które zabezpieczają połączenia internetowe. Hierarchiczna struktura CA pozwala na łatwiejsze zarządzanie certyfikatami, rozdzielając odpowiedzialność pomiędzy różne poziomy urzędów certyfikacji, co zwiększa bezpieczeństwo całego systemu. Gdy walidujemy dany certyfikat, weryfikowane jest całe drzewo.
Przypinanie SSL w systemie Android
Począwszy od Androida Nougat (Android 7.0), proces przypinania SSL w aplikacjach mobilnych został znacznie uproszczony dzięki wprowadzeniu specjalnej konfiguracji sieci. Programiści mogą teraz łatwo włączyć przypinanie certyfikatów, dodając odpowiednią konfigurację w pliku manifestu aplikacji. Konfiguracja ta opiera się na pliku XML, który definiuje zasady bezpieczeństwa sieci. Można w nim określić piny, które będą weryfikowane podczas nawiązywania połączenia z serwerem. Wystarczy dodać linijkę konfiguracyjną android:networkSecurityConfig=”@xml/[file]” do manifiest.xml. Sam plik konfiguracyjny musi zawierać piny do weryfikacji.
Następnie w network_security_config.xml umieszczonym w katalogu res/xml/ można zdefiniować konkretne piny certyfikatów, które aplikacja powinna weryfikować:
<network-security-config>
<domain-config>
<domain includeSubdomains=”true”>twojadomena.com
</domain>
<pin-set expiration=”2023-12-31″>
<pin digest=”SHA-256″>base64_encoded_pin</pin>
</pin-set>
</domain-config>
</network-security-config>
Dzięki takiemu podejściu przypinanie SSL stało się prostsze i bardziej elastyczne, eliminując potrzebę ręcznej implementacji tego mechanizmu w kodzie aplikacji.
Przypinanie SSL w Smali
Podczas manualnej implementacji przypinania SSL programista zazwyczaj dodaje weryfikację certyfikatu w kodzie aplikacji, co może być zlokalizowane i analizowane w Smali. Smali to język niskopoziomowy używany do pisania i dekompilacji plików DEX (Dalvik Executable), które są uruchamiane na maszynie wirtualnej Androida. Gdy aplikacja korzysta z przypinania SSL, często można to znaleźć w wygenerowanym kodzie Smali, zwłaszcza jeśli mamy do czynienia z ręczną implementacją przez programistę.
Jeśli certyfikat lub klucz publiczny jest zapisany w kodzie, można go znaleźć w zasobach lub ciągach tekstowych w kodzie Smali, w którym zazwyczaj będziemy szukać odwołań do klas takich jak TrustManager lub innych odpowiedzialnych za weryfikację certyfikatów.
Jeśli chcemy zmodyfikować lub ominąć przypinanie SSL w aplikacji (co może być przydatne podczas testów bezpieczeństwa), musimy znaleźć odpowiednie miejsce w kodzie, gdzie następuje weryfikacja certyfikatu. Zmieniając skróty lub inne parametry, można zmusić aplikację do akceptacji innego certyfikatu niż oryginalnie zamierzony. W Smali może to wyglądać np. tak:
.method public checkServerTrusted([Ljava/security/cert/
X509Certificate;Ljava/lang/String;)V
.locals 3
…
const-string v0, „twojadomena.com”
…
.end method
W tym przypadku należałoby znaleźć i zmodyfikować odpowiednie miejsce, które sprawdza, czy certyfikat serwera jest zgodny z przypiętym certyfikatem lub kluczem publicznym.
Metody przypinania SSL
Poniżej przedstawiamy wybrane metody przypinania SSL. W celu implementacji SSL pinningu można wykorzystać m.in. OkHttp, TrustManagera lub HttpURLConnection.
OKHTTP
Jedną z najprostszych metod implementacji SSL pinningu jest użycie klasy CertificatePinner z OkHttp. OkHttp to popularna biblioteka HTTP dla Androida i Javy, która umożliwia łatwe zarządzanie połączeniami sieciowymi. Ma wbudowaną funkcjonalność pinningu SSL, co pozwala na zwiększenie bezpieczeństwa komunikacji poprzez przypięcie certyfikatu lub klucza publicznego do połączenia. OkHttp umożliwia przypinanie certyfikatu lub klucza publicznego poprzez zdefiniowanie listy certyfikatów, którym aplikacja ufa.
Podczas nawiązywania połączenia OkHttp porównuje certyfikat serwera z przypiętym certyfikatem lub kluczem publicznym. Jeśli certyfikat nie pasuje do przypiętego, połączenie zostanie odrzucone. OkHttp upraszcza implementację pinningu SSL dzięki łatwej konfiguracji i dobremu wsparciu dokumentacyjnemu. Wersje OkHttp poniżej 2.1 są jednak podatne na ataki i nie uważa się ich za bezpieczne.
Za pomocą kilku linijek kodu w Javie można wygenerować żądanie zawierające weryfikację SSL pinningu:
CertificatePinner certificatePinner = new CertificatePinner.
Builder()
.add(„twojadomena.com”, „sha256/AAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA=”)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
TRUSTMANAGER
Inną metodą weryfikacji SSL pinningu jest użycie niestandardowych TrustManagerów, które mogą zweryfikować certyfikat na podstawie wstępnie zakodowanych wartości przechowywanych w magazynie kluczy. TrustManager to interfejs używany w Javie do weryfikacji certyfikatów SSL podczas nawiązywania połączeń. Poprzez dostosowanie własnego TrustManagera można kontrolować, które certyfikaty są akceptowane przez aplikację, umożliwiając w ten sposób przypinanie SSL. Implementując niestandardowego TrustManagera, programista może określić, jakie certyfikaty będą akceptowane. TrustManager weryfikuje certyfikat serwera i porównuje go z tymi wcześniej zdefiniowanymi i zaufanymi. Zapewnia także większą elastyczność i kontrolę nad procesem weryfikacji, jednak wymaga bardziej zaawansowanej implementacji. Niezbędne jest użycie funkcji takich jak X509TrustManager i SSLSocketFactory. Przykładowa implementacja w Javie wygląda następująco:
TrustManager[] trustManagers = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkServerTrusted(X509Certificate[]
chain, String authType) throws CertificateException {
// Weryfikacja przypięcia certyfikatu
if (/* warunek przypinania */) {
throw new CertificateException(„Certyfikat
nie jest zaufany!”);
}
}
// Inne metody TrustManager…
} };
SSLContext sslContext = SSLContext.getInstance(„TLS”);
sslContext.init(null, trustManagers, new SecureRandom());
HTTPURLCONNECTION
Klasycznym sposobem przeprowadzania weryfikacji jest użycie osobiście napisanego kodu, który może zweryfikować parametry certyfikatu i sprawdzić je za pośrednictwem HttpURLConnection. HttpURLConnection to standardowa klasa w Javie do zarządzania połączeniami HTTP. Choć nie oferuje wbudowanego mechanizmu przypinania SSL, można je zaimplementować, modyfikując konfigurację połączenia SSL za pomocą niestandardowego TrustManagera, który zweryfikuje certyfikat serwera przed ustanowieniem połączenia. HttpURLConnection jest szeroko stosowany i może być użyty w różnych środowiskach, jednak implementacja przypinania SSL wymaga więcej pracy w porównaniu do OkHttp. Gdy korzystamy z tej metody, lepiej jest ominąć kod, niż nim manipulować. Przykładowa implementacja w Javie została przedstawiona poniżej:
URL url = new URL(„https://twojadomena.com”);
HttpsURLConnection urlConnection = (HttpsURLConnection)
url.openConnection();
SSLContext sslContext = SSLContext.getInstance(„TLS”);
sslContext.init(null, trustManagers, new SecureRandom());
urlConnection.setSSLSocketFactory(sslContext.getSocket-
Factory());
InputStream in = urlConnection.getInputStream();
Każda z metod przypinania SSL – OkHttp, TrustManager i HttpURLConnection – oferuje różne poziomy elastyczności i kontroli nad procesem weryfikacji certyfikatów. Wybór zależy od specyficznych potrzeb aplikacji, poziomu bezpieczeństwa oraz łatwości implementacji. OkHttp jest najbardziej intuicyjną opcją dla aplikacji na Androida, TrustManager daje największą kontrolę, a HttpURLConnection może być użyteczny w bardziej złożonych środowiskach.
Przed łamaniem
SSL pinning jest jedną z metod zabezpieczenia aplikacji mobilnych i webowych przed atakami typu man-in-the-middle, poprzez ścisłe powiązanie aplikacji z zaufanym certyfikatem. Choć to rozwiązanie znacząco podnosi poziom bezpieczeństwa, w niektórych przypadkach może stanowić wyzwanie, zwłaszcza w trakcie testowania aplikacji lub w analizie bezpieczeństwa.
W tym artykule przybliżyliśmy teoretyczne podstawy i różne sposoby przypinania SSL. W następnej części przejdziemy do bardziej zaawansowanych zagadnień i omówimy metody, które mogą być wykorzystane do obejścia SSL pinningu, zarówno w kontekście badań nad bezpieczeństwem, jak i testów penetracyjnych. Dowiemy się, jak takie techniki działają, a także jak się przed nimi bronić, aby maksymalnie zabezpieczyć aplikacje.
Autor
Adam Kamiński
Autor jest trenerem modeli AI i entuzjastą ofensywnego podejścia do cyberbezpieczeństwa.