mObywatel mObywatel mObywatel ANALIZA STATYCZNA · v4.87.2

W skrócie. Niezależna, kontradyktoryjna analiza statyczna produkcyjnego pakietu APK. Zamiast orzekać o bezpieczeństwie aplikacji bądź o jej rzekomej wadliwości, przedstawiam wyłącznie to, co wynika z samego artefaktu — popierając każde twierdzenie ścieżką pliku i numerem linii oraz jawnie oddzielając fakt statyczny od wniosku i od hipotezy wymagającej testu na urządzeniu. Wyłania się z tego obraz kodu dojrzałego i utwardzonego warstwowo, obciążonego wszakże jedną spójną słabością na granicy zaufania: podpisane dokumenty bywają przyjmowane bez weryfikacji wystawcy, a poufność bywa mylona z uwierzytelnieniem. Działającego exploita nie prezentuję; wskazuję natomiast zestaw testów P0, które tę wątpliwość ostatecznie rozstrzygną.

Niezależna rewizja bezpieczeństwa mObywatel 4.87.2 dla Androida

Stan dokumentu: raport z analizy statycznej, rewizja 1 z 25 sierpnia 2026 r.
Autor: Marek Wesołowski (WESMAR), 2026
Badany pakiet: pl.nask.mobywatel, 4.87.2 (6426), versionCode 56416
Cel: niezależna weryfikacja techniczna artefaktu
Zasada oceny: ani domniemanie bezpieczeństwa, ani domniemanie winy; wnioski zależą od dowodów i jawnie określonego modelu zagrożeń.


1. Najpierw istotne ograniczenie materiału publicznego

Publicznie udostępniony materiał, który można przypisać projektowi Android, zawiera 192 pliki Kotlin. Przegląd drzewa wykazał, że jest to przede wszystkim warstwa interfejsu i system komponentów wizualnych. W tym materiale nie znaleziono:

  • plików Gradle ani settings.gradle;
  • AndroidManifest.xml;
  • implementacji warstwy sieciowej, walidacji TLS i mechanizmu przejrzystości certyfikatów (Certificate Transparency, CT);
  • implementacji kryptografii, Android Keystore i przepływów uwierzytelniania;
  • kompletnej konfiguracji produktu pozwalającej zbudować i odtworzyć oficjalny APK.

Oznacza to, że publiczny materiał nie wystarczał do przeprowadzenia audytu zachowania aplikacji produkcyjnej. Pozostałą część trzeba było odtworzyć z kodu DEX i zasobów podpisanego APK. Jest to uczciwe i użyteczne źródło informacji o wykonywanej binarce, ale nie jest odpowiednikiem pełnego kodu źródłowego: dekompilator traci nazwy, typy i część struktury sterowania, a niektóre funkcje Kotlin/coroutines rekonstruuje błędnie.

W konsekwencji:

  • fakty z manifestu, zasobów, stałych i prostych ścieżek są zwykle wysoce pewne;
  • złożone ścieżki sterowania były potwierdzane drugim trybem dekompilacji lub dowodem wykonaniowym;
  • nie przypisano aplikacji podatności tylko dlatego, że niebezpieczna klasa występuje w pliku APK;
  • nie uznano aplikacji za bezpieczną tylko dlatego, że ustawienie produkcyjne ma poprawną wartość domyślną.

Publikacja części interfejsu użytkownika może wspierać przejrzystość wzornictwa, lecz nie umożliwia niezależnej kontroli właściwości bezpieczeństwa produktu. Jest to ograniczenie audytowalności, a nie samo w sobie dowód podatności.

Zamknięcie pozostałej części implementacji nie jest kontrolą bezpieczeństwa. Uniemożliwia ono zewnętrznym badaczom sprawdzenie założeń, odtworzenie kompilacji i szybkie wykrywanie regresji. Równocześnie sama niejawność kodu nie stanowi dowodu, że znajduje się w nim konkretna podatność — taki zarzut nadal wymaga śladu technicznego. W niniejszej pracy brak przejrzystości traktowany jest więc jako obniżenie poziomu niezależnego zapewnienia, a nie jako zamiennik analizy.


2. Streszczenie wykonawcze

Wstępny przegląd tego artefaktu wskazał kilka realnie niebezpiecznych konstrukcji obecnych w APK, przypisał im jednak wpływ i dostępność, których kod nie potwierdza. Najpoważniejsze błędy to:

  1. utożsamienie wyjątku HTTP dla gstatic.com z możliwością podmiany fontów, JavaScriptu i dowolnych zasobów;
  2. uznanie prywatnej flagi aplikacji i nieosiągalnego w produkcji ekranu deweloperskiego za zdalnie dostępny mechanizm wyłączenia TLS;
  3. błędna interpretacja ksRandomizer jako losowania aliasu klucza;
  4. zalecenie android:autoVerify dla własnego schematu URI, mimo że weryfikacja Android App Links dotyczy adresów HTTP/HTTPS;
  5. potraktowanie obecności bibliotek AndroidX/Tink jako dowodu, że chronią one konkretną bazę i krytyczne dane aplikacji.

Jednocześnie pierwotny raport pominął rzeczywistą słabość mechanizmu przejrzystości certyfikatów (CT):

Lista dzienników CT jest pobierana po HTTP i wprawdzie poprawnie weryfikowana podpisem Google, ale aplikacja nie egzekwuje jej świeżości, wersji, stanu dziennika ani przedziału czasowego. Aktywny napastnik sieciowy może więc ponownie podawać starszą, nadal poprawnie podpisaną listę.

Wpływ jest warunkowy. Samo ponowne podanie starszej listy nie przełamuje TLS i nie pozwala wstrzyknąć dowolnej treści. Może jednak osłabić dodatkową kontrolę CT w scenariuszu, w którym napastnik dysponuje już certyfikatem akceptowanym przez system oraz SCT pochodzącym z dziennika, któremu aplikacja nie powinna już ufać. Oceniono to jako ryzyko średnie dotyczące obrony warstwowej; nie jest to samodzielne przejęcie sesji.

Poza tym potwierdzono:

  • oficjalny certyfikat podpisujący base.apk, zgodny z odciskiem opublikowanym przez domenę mobywatel.gov.pl;
  • bezpieczne wartości domyślne TLS, przypinania certyfikatów, CT, obsługi SSL w WebView i FLAG_SECURE w wariancie produkcyjnym;
  • niebezpieczne ścieżki TrustAll, permisywny HostnameVerifier oraz SslErrorHandler.proceed() pozostawione w produkcyjnym APK, lecz bez wykazanego przełącznika dostępnego zwykłej aplikacji trzeciej, użytkownikowi lub napastnikowi sieciowemu;
  • starszy wariant autoryzacji OAuth e-Doręczeń, wybierany serwerową flagą funkcjonalną, który nie generuje ani nie porównuje state i wymienia kod z code_verifier=null; równolegle obecna nowsza implementacja stosuje losowy state oraz PKCE S256, co stanowi zarówno dobry wzorzec, jak i dowód realnej luki regresyjnej między wariantami;
  • na Androidzie 8–9 nazwa załącznika z serwerowego Content-Disposition może zostać włączona bezpośrednio do ścieżki w pamięci współdzielonej, bez normalizacji i kontroli pozostania w katalogu Downloads; daje to warunkowy zapis nowego pliku poza katalogiem docelowym, ale kod nie nadpisuje pliku już istniejącego, a rzeczywistą kontrolę nazwy przez innego użytkownika należy potwierdzić testem;
  • poprawne odrzucanie listy CT o błędnym podpisie i zachowanie ostatniej znanej poprawnej listy;
  • rozsądne zabezpieczenia manifestu: wyłączone kopie danych, Safe Browsing, prywatny FileProvider i uprawnienia systemowe na eksportowanych komponentach bibliotek;
  • produkcyjne włączenie FLAG_SECURE, istotne w kontekście wcześniejszego CVE dotyczącego podglądu danych w przełączniku aplikacji systemu iOS; test dynamiczny regresji Androida nadal jest konieczny.

Inwentaryzacja łańcucha dostaw ujawniła 324 zadeklarowane moduły Maven, w tym 28 artefaktów oznaczonych alpha lub beta. Trzy spakowane moduły Bouncy Castle 1.83 są objęte znanymi poprawkami bezpieczeństwa; automatyczne zapytanie OSV wskazało trzy komunikaty, a ręczna kontrola not projektu źródłowego ujawniła dalsze pozycje pominięte przez to źródło danych. Poprawiona wersja 1.84 była dostępna od 28 kwietnia 2026 r., czyli około trzy miesiące przed badanym wydaniem. Aplikacja rzeczywiście rejestruje dostawcę kryptograficznego BC i używa CMS, PKCS#12 oraz ASN.1, więc nie jest to martwa zależność.

Nie znaleziono wywołania konkretnych mechanizmów GOST, LDAP, Composite Signature ani FrodoKEM poza kodem biblioteki. Jest to potwierdzony dług aktualizacyjny i potencjalna powierzchnia podatności, ale nie dowód osiągalnej eskalacji tymi czterema CVE. Znaleziono natomiast odrębny wzorzec aplikacyjny: klient tworzy i zapisuje obiekty CMSSignedData, a następnie odczytuje getSignedContent() bez znalezionej lokalnej weryfikacji podpisujących. Brak obejmuje więc zidentyfikowane punkty przyjęcia i wykorzystania danych, a nie tylko ekran prezentacji. Wpływ zależy od tego, czy napastnik może dostarczyć lub zmienić kontener, ale hipoteza wymaga testu P0, ponieważ treść zasila modele dokumentów.

Ocena procesu inżynierskiego jest krytyczna. W produkcie przetwarzającym państwową tożsamość nie powinno być konieczne tłumaczenie, dlaczego kod TrustAll nie może trafić do wydania produkcyjnego, dlaczego aktywnie używany dostawca kryptograficzny z opublikowaną od kwietnia aktualizacją nie powinien pozostać w lipcowym wydaniu ani dlaczego SignedData musi być zweryfikowane przed użyciem treści. Każdy z tych problemów osobno może mieć warunkowy wpływ; razem świadczą o braku wystarczająco twardych bram jakości artefaktu. Publiczne udostępnienie wyłącznie warstwy interfejsu użytkownika dodatkowo uniemożliwia społeczną weryfikację, czy bramy takie istnieją i działają.

Nie ma podstaw do oceny, ile środków finansowych zostało zmarnowanych ani kto personalnie odpowiada za decyzje — badany materiał nie zawiera budżetów, umów, historii przeglądów ani wyjątków ryzyka. Można natomiast uczciwie stwierdzić, że rezultat techniczny i zakres publikacji nie odpowiadają poziomowi audytowalności oczekiwanemu od krytycznej aplikacji publicznej.

Nie ma podstaw do ogólnego komunikatu „aplikacja jest bezpieczna”. Wynik jest migawką analizy statycznej wersji 4.87.2. Najważniejsze rozstrzygnięcia wymagające testu urządzeniowego opisano w sekcji 12.


3. Identyfikacja i pochodzenie badanego artefaktu

Właściwość Wynik
Archiwum mObywatel.apkm
SHA-256 archiwum 534df84d382f05a28a0f1fb6ff48950a1e182e3f126644e495ce945f873b1078
Badana binarka decompiling/base.apk
SHA-256 base.apk 1ede2b031ffb7e5036308dda8ad08c98ccd0d30316c3336cbf7b93caf86ecb72
Pakiet pl.nask.mobywatel
Wersja 4.87.2 (6426)
VersionCode 56416
Min / target / compile API 26 / 36 / 36
Data wpisana w metadanych APKM 29 lipca 2026 r.

Weryfikator Android apksig 9.3.2 potwierdził integralność base.apk:

verified = true
v1 = false
v2 = true
v3 = true
v4 = false

Certyfikat podpisujący:

Subject/Issuer: CN=Android, OU=Android, O=Google Inc.,
                L=Mountain View, ST=California, C=US
SHA-256: CB:4F:1E:A4:F0:BE:4A:91:EA:80:34:97:DD:A6:9F:31:
         84:5C:9A:98:F4:39:03:C5:60:17:CF:4A:B8:77:D8:9F

Sam ogólny podmiot O=Google Inc. nie dowodzi autentyczności aplikacji. Rozstrzygający jest fakt, że identyczny odcisk dla pakietu pl.nask.mobywatel publikuje oficjalny plik Digital Asset Links. Potwierdza to użycie klucza autoryzowanego przez domenę mObywatel. Nie dowodzi natomiast, że plik o podanym hashu jest obecnie identyczny z egzemplarzem pobieranym z każdego kanału dystrybucji; do takiego twierdzenia potrzebne byłoby bezpośrednie porównanie artefaktów.


4. Metoda, skala pewności i model zagrożeń

4.1. Zakres wykonany

  • inwentaryzacja APKM i metadanych manifestu;
  • kryptograficzna weryfikacja podpisu APK i powiązania z oficjalną domeną;
  • dekompilacja DEX i zasobów za pomocą JADX;
  • ponowna dekompilacja problematycznej funkcji CT w trybie zapasowym (fallback);
  • analiza konfiguracji sieci, TLS, przypinania certyfikatów, CT, WebView, obsługi odnośników kierujących do aplikacji, magazynu kluczy, logowania, komponentów eksportowanych, kopii danych i ścieżek zapisu plików;
  • pobranie bieżącej listy CT, weryfikacja jej podpisu kluczem osadzonym w aplikacji i analiza schematu danych;
  • regresyjna kontrola publicznie opisanego CVE-2025-11598;
  • weryfikacja każdej materialnej tezy pierwotnego raportu.

4.2. Poza zakresem tej rewizji

  • testy na fizycznym urządzeniu i emulatorze;
  • logowanie do konta obywatela i ocena autoryzacji po stronie serwera;
  • testowanie mutacyjne parserów i interfejsów Binder/JNI;
  • analiza infrastruktury serwerowej i procedur operacyjnych;
  • pełny przegląd bibliotek natywnych;
  • porównanie ze źródłami, które nie zostały jeszcze udostępnione audytorowi.

4.3. Znaczenie ocen

Oznaczenie Znaczenie
Potwierdzone Zachowanie wynika bezpośrednio z artefaktu lub zostało odtworzone eksperymentalnie.
Częściowo potwierdzone Mechanizm istnieje, ale pełny wpływ wymaga warunku lub testu dynamicznego.
Nieudowodnione Hipoteza jest możliwa, ale obecny materiał nie wykazuje ścieżki wykorzystania.
Obalone Kod lub eksperyment przeczy twierdzeniu raportu pierwotnego.

Ocena zakłada przede wszystkim zwykłego napastnika sieciowego lub złośliwą aplikację bez uprawnień administratora systemu (root). Uzyskanie takich uprawnień, instrumentacja Frida, modyfikacja prywatnego katalogu aplikacji i przepakowanie APK są omawiane osobno, ponieważ napastnik o takich możliwościach może bezpośrednio przechwytywać dane procesu i omijać kontrolę TLS bez używania flag aplikacji.

4.4. Granice dowodu i zaufania

flowchart LR PUB[Publiczne 192 pliki Kotlin
głównie interfejs użytkownika]:::limited APK[Podpisany base.apk
kod DEX + zasoby]:::evidence DOMAIN[Oficjalny assetlinks.json
odcisk certyfikatu]:::evidence DECOMP[Dekompilacja JADX
tryb standardowy + zapasowy]:::method LIVE[Zasoby zdalne
CT / źródła publiczne]:::method REPORT[Wnioski audytu
z poziomem pewności]:::result MISSING[Brak pełnych źródeł,
zaplecza serwerowego i śladu wykonania]:::gap PUB --> REPORT APK --> DECOMP --> REPORT DOMAIN --> REPORT LIVE --> REPORT MISSING -. ogranicza .-> REPORT classDef evidence fill:#d9f2e6,stroke:#16784b,color:#102a20; classDef method fill:#e2ecff,stroke:#345da8,color:#12213d; classDef result fill:#fff1c7,stroke:#a66a00,color:#342300; classDef limited fill:#f2e8ff,stroke:#7651a8,color:#261b36; classDef gap fill:#ffe0e0,stroke:#ad3030,color:#3d1111;

5. Zestawienie ustaleń

ID Ustalenie Ocena Pewność
MOB-A-01 Możliwość podstawienia starszej, prawidłowo podpisanej listy dzienników CT pobieranej po HTTP Średnie Potwierdzone zachowanie; wpływ warunkowy
MOB-A-02 Ścieżki akceptacji połączenia mimo błędu TLS lub WebView pozostawione w binarce produkcyjnej Niskie Potwierdzone istnienie; brak zwykłej ścieżki aktywacji
MOB-A-03 Własne schematy URI przenoszące tokeny: kolizja procedur obsługi i możliwość dostarczenia jawnego Intentu Niskie / do testu Częściowo potwierdzone
MOB-A-04 Aktywnie używany Bouncy Castle 1.83 mimo wcześniejszej dostępności 1.84; źródło danych OSV nie obejmuje pełnej listy projektu źródłowego Średnie operacyjne / P0 Obecność i użycie potwierdzone; wpływ poszczególnych CVE wymaga potwierdzenia osiągalności kodu
MOB-A-05 Przyjęcie, zapis i odczyt CMSSignedData bez znalezionej lokalnej weryfikacji podpisujących Średnie / P0 do testu Zachowanie potwierdzone; wektor dostarczenia warunkowy
MOB-A-06 Globalna, stała ośmiobajtowa sól PBKDF2 dla pakietów aktywacyjnych legitymacji studenckiej Niskie–Średnie / P0 do charakterystyki Stała sól i parametry KDF potwierdzone; entropia kodu i wykonalność ataku poza aplikacją wymagają testu
MOB-A-08 Eksportowalny klucz prywatny certyfikatu UNIVERSITY i hasło PKCS#12 współlokowane w pakiecie aktywacyjnym; zapis nieopakowanego PKCS#8 w SQLCipher Średnie, warunkowo Wysokie po P0 Cykl życia klucza potwierdzony; odzyskanie poza aplikacją i skutki użycia klucza wymagają testu
MOB-A-09 Starszy wariant OAuth e-Doręczeń bez state i PKCE, pozostawiony jako ścieżka wybierana flagą serwerową Średnie / warunkowe Brak obu zabezpieczeń potwierdzony; aktywny wariant i praktyczne wstrzyknięcie kodu wymagają P0
MOB-A-10 Nazwa załącznika z Content-Disposition używana bez kanonikalizacji w bezpośrednio budowanej ścieżce zapisu na API 26–28 Niskie–Średnie / P1 do testu Łańcuch klienta potwierdzony; kontrola nazwy przez atakującego i wpływ serwera warunkowe
MOB-O-01 Zdalne logowanie pełnego URL dla części profili HTTP może ujawnić parametry zapytania Obserwacja Nieudowodniony wrażliwy przepływ
MOB-O-02 FileProvider obejmuje całą pamięć zewnętrzną, choć nie jest eksportowany Utwardzenie Potwierdzone, bez wykazanego wycieku
MOB-O-03 Rozbudowany zakres tagów urządzenia i zdarzeń bezpieczeństwa wysyłanych do Sentry Prywatność / do testu Zakres konfiguracji potwierdzony
MOB-O-04 Brak znalezionej ochrony aplikacyjnej przed pełnym i częściowym zasłonięciem ekranów operacji wrażliwych Utwardzenie / do testu Brak mechanizmów potwierdzony statycznie; skuteczna akcja przez nakładkę nieudowodniona

Nie nadano osobnych identyfikatorów dla rzekomego wstrzykiwania czcionek/JavaScriptu, zdalnej zmiany flagi TLS, „losowania aliasu” przez ksRandomizer ani programowego przejścia na słabszy wariant bez StrongBox jako CWE-326, ponieważ te tezy są odpowiednio obalone lub nieudowodnione.

Identyfikator MOB-A-07 pozostaje zarezerwowany dla potencjalnego rozwinięcia MOB-A-03 dotyczącego mobywatel://eqsig. Analiza statyczna potwierdza zewnętrzny token i nieweryfikowalny własny schemat URI, ale bez testu wiązania tokenu z sesją, walidacji po stronie serwera i ekranu zgody nie wykazano jeszcze wpływu niezależnego od już opisanego MOB-A-03.

Potencjalne ustalenie nazwane w materiale porównawczym „WebView akceptuje połączenie mimo błędu TLS i zezwala na dostęp do plików” zostało scalone z wcześniejszym MOB-A-02, ponieważ opisuje tę samą klasę granicy WebView i nie powinno sztucznie zwiększać liczby wyników. Identyfikator MOB-A-09 w niniejszym raporcie oznacza niezależną wadę transakcyjnego wiązania OAuth opisaną w sekcji 8.2.


6. MOB-A-01 — możliwość podstawienia starszej, prawidłowo podpisanej listy dzienników CT

Ocena: średnia, mechanizm obrony w głąb
Atakujący: aktywny napastnik na ścieżce sieciowej
Warunki wpływu: dodatkowo certyfikat akceptowany przez system oraz SCT z dziennika, który po wydaniu starszej listy utracił zaufanie albo zmienił dopuszczalny przedział
Pewność: potwierdzona akceptacja starszej listy; wpływ eksploatacyjny zależny od PKI i historii dzienników

6.1. Dowód

Konfiguracja zezwala na HTTP do gstatic.com:

<domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="true">gstatic.com</domain>
</domain-config>

Dowód: network_security_config.xml.

Jedyny aktywny literał HTTP do tej domeny znaleziony w zasobach aplikacji to:

http://www.gstatic.com/ct/log_list/v3/log_list.zip

Dowód: strings.xml.

Kod pobiera ZIP profilem NonCTRaw, wyodrębnia log_list.json i log_list.sig, ograniczając ich wielkość odpowiednio do 1 MiB i 512 B. Następnie weryfikuje podpis kluczem publicznym osadzonym w APK. Dowody: iy3/c.java, iy3/c.java, iy3/c.java.

Pierwsza standardowa dekompilacja funkcji weryfikującej była błędna i sugerowała akceptację nieważnego podpisu. Dekompilacja tej samej klasy w trybie zapasowym (fallback) wykazała przeciwnie:

  • brak klucza publicznego → odrzucenie;
  • błędny podpis → odrzucenie;
  • poprawny podpis → akceptacja.

Bieżący ZIP pobrany podczas badania miał SHA-256:

164e3b2368fcb84763bbb6fb4c3839d53d69c2ca469515365bd0e762613b50ce

Weryfikacja log_list.sig kluczem z APK dała Verified OK. To obala hipotezę, że napastnik może podmienić listę dowolną treścią.

Problem dotyczy świeżości. DTO zawiera pola version i log_list_timestamp, ale w badanej implementacji nie znaleziono ich użycia do kontroli monotoniczności ani maksymalnego wieku. Dowód: LogListDto.java. Model pojedynczego dziennika pomija ponadto pola bieżącego formatu, takie jak stan dziennika i przedział czasowy; przechowuje głównie identyfikator i klucz. Dowód: LogDto.java.

Po poprawnej weryfikacji aplikacja zapisuje lokalny czas pobrania jako cacheTime. W efekcie historyczny, lecz prawidłowo podpisany dokument dostarczony ponownie po HTTP wygląda lokalnie jak świeżo pobrany. Dowód: iy3/c.java.

6.2. Przepływ

sequenceDiagram autonumber participant G as Google / gstatic participant A as Archiwum napastnika participant N as Aktywny MITM HTTP participant M as mObywatel 4.87.2 participant C as Pamięć podręczna listy CT G->>A: Starszy log_list.zip
JSON + prawidłowy podpis A->>N: Zachowany historyczny artefakt N->>M: Ponowne podanie starszego ZIP przez HTTP M->>M: Weryfikacja podpisu = poprawna M->>M: Kontrola znacznika czasu = brak M->>M: Kontrola cofnięcia wersji = brak M->>M: Kontrola stanu / przedziału czasowego = brak M->>C: Zapis listy i lokalnego czasu pobrania Note over M,C: Integralność zachowana,
świeżość nieudowodniona

6.3. Czego ten błąd nie oznacza

  • Nie pozwala podpisać własnej listy dzienników CT.
  • Nie pozwala podmieniać czcionek, JavaScriptu ani innych zasobów aplikacji; nie znaleziono takiego odbiorcy dla tego wyjątku HTTP.
  • Nie omija samodzielnie systemowej walidacji łańcucha certyfikatu.
  • Nie jest dowodem ujawnienia informacji wrażliwych przez HTTP — pobierana lista jest publiczna.

6.4. Zalecenie

  1. Zmienić punkt końcowy na HTTPS i usunąć zezwolenie na ruch nieszyfrowany dla całej domeny gstatic.com.
  2. Zachować podpis aplikacyjny jako niezależną warstwę ochrony.
  3. Egzekwować maksymalny wiek log_list_timestamp oraz monotoniczny wzrost version.
  4. Odczytywać i egzekwować stan dziennika oraz jego temporal_interval względem czasu SCT/certyfikatu.
  5. Dopuszczać ostatnią znaną poprawną listę tylko przez jawnie ustalony okres awaryjny.
  6. Dodać testy: ponowne podanie starszej podpisanej wersji, wersja cofnięta, znacznik czasu z przyszłości, dziennik wycofany, błędny podpis i uszkodzony ZIP.

7. MOB-A-02 — niebezpieczne ścieżki TLS/WebView w produkcyjnym APK

Ocena: niska jako higiena i odporność produktu
Pewność: istnienie kodu potwierdzone; dostępność dla zwykłego napastnika niepotwierdzona i w standardowej konfiguracji zaprzeczona

7.1. Co rzeczywiście znajduje się w APK

Konfiguracja klienta HTTP pobiera podczas działania flagę HTTP_CLIENT_SSL. Jeżeli jest fałszywa, fabryka ustawia permisywny HostnameVerifier i TrustAllX509TrustManager. Dowód: pl/gov/coi/common/network/o.java.

Wspólny WebViewClient wywołuje SslErrorHandler.proceed(), gdy odpowiadająca mu konfiguracja SSL jest wyłączona. Dowód: t80/h.java.

Ten klient jest wspólny dla wielu ekranów. Ustawia parametry dokładnie z obiektu konfiguracji: dostęp do plików, JavaScript, pamięć DOM, treść mieszaną i pamięć podręczną: t80/l.java. Domyślna klasa bazowa ma restrykcyjne wartości: JavaScript false, pamięć DOM false, allowFileAccess=false oraz mixedContentMode=1, czyli MIXED_CONTENT_NEVER_ALLOW: t80/p.java. Konkretne ekrany świadomie rozszerzają te uprawnienia.

Rodzina WebView SSL JavaScript Pamięć DOM Dostęp do plików Treść mieszana Ocena statyczna
płatności i zapis karty (e32, mt3, s32) na stałe true true domyślnie false false NEVER_ALLOW poprawne odrzucenie przy błędzie
podglądy i proste treści (qu3) na stałe true true true false NEVER_ALLOW rozsądne, bez mostu Java
OAuth e-Doręczeń / Keycloak (a22, ys3) lokalna flaga WEB_VIEW_SSL true true false NEVER_ALLOW bezpieczne przy produkcyjnym true; krytyczny skutek błędnego wariantu
usługi online (f43) lokalna flaga WEB_VIEW_SSL true true true NEVER_ALLOW zbędne rozszerzenie powierzchni; nie wykazano samodzielnego odczytu pliku

Ścieżka usług internetowych pobiera zdalny URL, obsługuje POST, pobieranie plików oraz systemowy wybór pliku, a konfiguracja ustawia allowFileAccess=true: f43/b.java, e43/u.java. Samo to ustawienie nie przełamuje zasady wspólnego pochodzenia i w tym materiale nie wykazuje odczytu lokalnego pliku przez zdalną stronę. Jest jednak niepotrzebnym uprawnieniem dla komponentu przetwarzającego treść z sieci. Należy je wyłączyć i testować nawigacje file://, content://, przekierowania oraz systemowy wybór pliku.

Wspólny klient buduje komunikaty diagnostyczne z pełnego URL i po onPageFinished pobiera całe document.body.outerHTML: t80/h.java. W badanym wariancie produkcyjnym nie jest to potwierdzony wyciek: wywołania trafiają na poziom diagnostyczny do q00.a, którego metody debug/info odrzucają treść, a dopiero błąd jest przekazywany do Sentry: q00/a.java. To pozytywny wynik dla badanego odbiornika logów, ale kod powinien przestać konstruować pełny DOM i URL, ponieważ podmiana implementacji mechanizmu logowania w innym wariancie natychmiast zmieni tę właściwość bezpieczeństwa.

Przypinanie certyfikatów jest także zależne od flag, a profil NonCTRaw ma dodatkową ścieżkę akceptacji mimo błędu: jeśli nie uda się utworzyć oczekiwanego menedżera zaufania, używa wariantu permisywnego. Dotyczy to danych podpisanych na poziomie aplikacji, m.in. listy CT; nie należy automatycznie przenosić wpływu na główne API.

7.2. Dlaczego raport pierwotny zawyżył ryzyko

Wartości produkcyjne są bezpieczne:

HTTP_CLIENT_SSL          = true
CERTIFICATE_TRANSPARENCY = true
HTTP_CLIENT_CERT_PINNING = true
WEB_VIEW_SSL             = true
WEB_VIEW_DEBUGGING       = false

Dowód wartości domyślnych: a04/c.java.

Lokalne nadpisania trafiają do prywatnych SharedPreferences. Manifest wyłącza backup, a reguły ekstrakcji wykluczają dane. Zwykła aplikacja trzecia i napastnik sieciowy nie mogą zapisać tego pliku.

Repozytorium flag składa wartości domyślne z lokalnymi wpisami i nie pobiera WEB_VIEW_SSL z listy flag serwerowych: e04/b.java. Rozgałęzienie f04.e ma osobne wyjątki dla NURSE_CARD, ale dla WEB_VIEW_SSL po sprawdzeniu profilu audytowego/testowego odczytuje właśnie lokalne repozytorium. Co istotne, profile isSecurityAudit i isAutomaticTest wymuszają dla tej flagi false; jest to celowe ułatwienie testów MITM, a nie zdalny przełącznik produkcji. Taki wyjątek nadal powinien istnieć wyłącznie w osobnym artefakcie testowym.

Kod ekranu deweloperskiego istotnie jest skompilowany, ale produkcyjny graf zależności wstrzykuje implementację modułu zarządzającego, której isAvailable() zwraca false i która nie wykonuje nawigacji. Główna aktywność nie eksportuje osobnego ekranu deweloperskiego. Sama obecność oq1.m nie dowodzi osiągalności interfejsu.

flowchart TD NET[Atakujący sieciowy]:::ordinary APP[Zwykła aplikacja trzecia]:::ordinary USER[Użytkownik interfejsu produkcyjnego]:::ordinary ROOT[Uprawnienia root / Frida
przepakowany APK]:::priv DEV[Moduł funkcji deweloperskich
produkcja: isAvailable = false]:::guard PREF[Prywatne SharedPreferences
HTTP_CLIENT_SSL]:::state DEC{sslEnabled?} SAFE[Systemowy TrustManager
przypinanie certyfikatów / CT]:::safe UNSAFE[TrustAll + permisywna nazwa hosta
przypinanie wyłączone]:::danger NET -. brak zapisu .-> PREF APP -. izolacja Androida .-> PREF USER -. DEV .-> DEV DEV -. niedostępny .-> PREF ROOT --> PREF PREF --> DEC DEC -->|true, domyślnie w produkcji| SAFE DEC -->|false| UNSAFE classDef ordinary fill:#e2ecff,stroke:#345da8,color:#12213d; classDef priv fill:#ffe0e0,stroke:#ad3030,color:#3d1111; classDef guard fill:#f0f0f0,stroke:#666,color:#222; classDef state fill:#fff1c7,stroke:#a66a00,color:#342300; classDef safe fill:#d9f2e6,stroke:#16784b,color:#102a20; classDef danger fill:#ffc7c7,stroke:#b00020,color:#3b0008;

Stąd klasyfikacja Wysokie/CWE-295 jako zdalnie sterowalnego obejścia zabezpieczeń jest nieuzasadniona. Konstrukcja nadal jest zła z perspektywy obrony warstwowej: błąd wariantu, błędne wstrzyknięcie zależności lub przyszła regresja może aktywować kod akceptujący połączenie mimo błędu.

7.3. Zalecenie

  • usunąć klasy TrustAll, permisywny HostnameVerifier i SslErrorHandler.proceed() z wariantu produkcyjnego przez rozdzielenie zestawów źródeł;
  • dla hostów produkcyjnych bezwzględnie odrzucać połączenie przy błędzie: wyłączenie TLS powinno kończyć inicjalizację niepowodzeniem;
  • nie utrzymywać lokalnie nadpisywalnych flag bezpieczeństwa w wariancie produkcyjnym;
  • ustawić allowFileAccess=false we wszystkich WebView sieciowych oraz dodać jawne listy dozwolonych schematów i hostów dla nawigacji uprzywilejowanych;
  • usunąć pobieranie pełnego DOM i usuwać parametry zapytania oraz fragment adresu przed utworzeniem komunikatu logowania, nawet jeżeli obecny produkcyjny odbiornik odrzuca poziom diagnostyczny;
  • dodać test artefaktu wydania, który wykrywa obecność sygnatur klas TrustAll i wywołania SslErrorHandler.proceed();
  • przetestować końcowy podpisany pakiet, a nie tylko wariant pośredni z ciągłej integracji (CI).

8. MOB-A-03 — własne schematy URI i tokeny

8.1. Eksportowana aktywność i walidacja URI

Manifest eksportuje MainActivity dla:

  • mobywatel://app/institutions;
  • mobywatel://eqsig;
  • zweryfikowanego https://api.mobywatel.gov.pl/application/diia_confirmed.

Dowód: AndroidManifest.xml.

Procedura obsługi institutions odczytuje parametr qrCode. Wewnątrz aplikacji sprawdza akcję VIEW, obecność URI i wyłącznie to, czy ostatni segment ścieżki jest równy institutions; nie porównuje ponownie schematu ani hosta: bk3/e.java. Filtr manifestu ogranicza zwykłe rozpoznanie niejawnego Intentu do mobywatel://app/institutions, lecz dowolna aplikacja może skierować jawny Intent do eksportowanej MainActivity z innym schematem lub hostem i pasującym ostatnim segmentem. To nie dowodzi przejęcia procesu: qrCode uruchamia przebieg weryfikacji/logowania i nie znaleziono lokalnego pominięcia autoryzacji. Jest jednak konkretną luką walidacji granicy wejściowej, którą trzeba zamknąć przed analizą jednorazowości tokenu po stronie serwera.

Procedura obsługi podpisu kwalifikowanego jest pod tym względem lepsza: sprawdza oczekiwany schemat i host, odczytuje token i rozpoczyna proces potwierdzenia tożsamości. Nie wykazano, aby samo dostarczenie wartości omijało logowanie, zgodę użytkownika lub walidację po stronie serwera.

Ryzyka własnego schematu są dwa:

  1. inna aplikacja może zarejestrować ten sam schemat i próbować przechwycić odnośnik wychodzący;
  2. inna aplikacja może jawnie uruchomić eksportowaną MainActivity z przygotowanym URI.

android:autoVerify nie rozwiązuje tego dla mobywatel://, ponieważ mechanizm zweryfikowanych linków aplikacyjnych Android App Links obejmuje domeny HTTP/HTTPS. Raport pierwotny zalecał więc środek technicznie niedopasowany.

Zalecenie: przenieść przebiegi zawierające tokeny do zweryfikowanych linków HTTPS, stosować token jednorazowy z krótkim czasem ważności (TTL), wiązaniem z odbiorcą, stanem i sesją (audience/state/session) oraz walidacją po stronie serwera. Do czasu testu z aplikacją kolidującą nie należy przedstawiać tego jako potwierdzonego przejęcia konta lub podpisu.

8.2. MOB-A-09 — starszy wariant OAuth e-Doręczeń bez state i PKCE

Ocena: średnia, warunkowa
Pewność: brak mechanizmów po stronie klienta potwierdzony; aktywność wariantu i skuteczność ataku wymagają testu P0
Klasa skutku: CSRF podczas logowania, wstrzyknięcie kodu autoryzacyjnego lub pomylenie sesji, bez statycznie wykazanego przejęcia konta

APK zawiera starszą implementację autoryzacji e-Doręczeń oraz nowsze mechanizmy autoryzacyjne. Starsza ścieżka y12.t uruchamia URL edorUrl bez dodania transakcyjnego state ani code_challenge: y12/t.java. Wywołanie zwrotne akceptuje każdy URL o schemacie mobywatel, którego ścieżka zaczyna się od /edelivery, pobiera sam parametr code i przekazuje go do wymiany tokenów; host, state i powiązanie z rozpoczętą transakcją nie są sprawdzane: y12/t.java.

Łańcuch nie urywa się na warstwie interfejsu użytkownika. Stara ścieżka tworzy sn0.x.Params(tokenUrl, code, null), więc codeVerifier jest jawnie pusty: y12/t.java. Repozytorium przekazuje tę wartość do pola formularza code_verifier punktu końcowego tokenów: zn0/y.java, tn0/h.java. Nie znaleziono alternatywnej wartości jednorazowej (nonce) ani lokalnego sekretu transakcyjnego w tym wariancie.

Nowszy moduł Keycloak obecny w tej samej aplikacji pokazuje wzorzec, który powinien obowiązywać wszędzie: generuje sekrety transakcji, dopisuje state, code_challenge i code_challenge_method=S256, porównuje zwrócony state, a do punktu końcowego tokenów przekazuje codeVerifier: vs3/p.java, vs3/p.java, vs3/p.java. Jest to dobra implementacja oraz silny dowód, że brak w y12.t nie wynika z ograniczenia platformy. Analiza statyczna nie pozwala utożsamić każdego wywołania vs3.p z następcą konkretnego ekranu y12.t, dlatego porównanie dotyczy własności zabezpieczeń, a nie deklaracji pełnej równoważności tras.

Wybór między starszym WebView a nowym modułem natywnym odbywa się przez serwerową flagę NATIVE_LOGIN_EDOR_MODULE_ANDROID: przypadek użycia zwraca false, jeżeli flagi brakuje albo jest nieaktywna, a interfejs użytkownika wybiera wtedy właśnie y12.t: xy1/i0.java, i64/c6.java. Analiza statyczna nie ujawnia bieżącej wartości flagi dla konkretnego użytkownika, regionu lub grupy wdrożeniowej. Dlatego nie wolno pisać, że wszyscy użytkownicy produkcyjni są obecnie podatni; równie nieuczciwe byłoby uznanie starego wariantu za martwy tylko dlatego, że istnieje nowszy.

Aktualne dobre praktyki OAuth (BCP) wymagają ochrony klienta przed CSRF i wstrzyknięciem kodu. Klient publiczny powinien używać PKCE, a gdy ochrona CSRF nie wynika z poprawnie związanego PKCE/OIDC nonce, powinien stosować jednorazowy state związany z przeglądarką lub innym programem pośredniczącym użytkownika. Źródło normatywne: RFC 9700, sekcja 2.1 i 2.1.1.

sequenceDiagram autonumber participant U as Użytkownik participant L as Starszy wariant y12.t participant AS as Serwer autoryzacyjny participant T as Punkt końcowy tokenów participant N as Nowszy vs3.p U->>L: Start autoryzacji L->>AS: edorUrl
bez state i code_challenge AS-->>L: mobywatel://.../edelivery?code=... L->>L: sprawdza schemat + początek ścieżki
nie sprawdza state/host L->>T: code + code_verifier=null Note over L,T: Brak transakcyjnego związania po stronie klienta U->>N: Start nowszego wariantu N->>AS: state + code_challenge S256 AS-->>N: code + state N->>N: sprawdza zgodność i jednorazowość state N->>T: code + code_verifier

Warunek podniesienia oceny: w teście P0 należy wymusić starszy wariant flagą serwerową, rozpocząć dwie równoległe transakcje na kontrolowanych kontach i sprawdzić, czy kod z transakcji A można wstrzyknąć do wywołania zwrotnego B lub dostarczyć przez kontrolowaną nawigację WebView. Należy także potwierdzić dokładne dopasowanie zwrotnego URI po stronie serwera, jednorazowość kodu, wiązanie kodu z klientem oraz zachowanie przy braku lub powtórzeniu state. Do tego czasu raport stwierdza brak wymaganych zabezpieczeń klienta, a nie udane przejęcie sesji.

Korekta: usunąć starszy wariant albo nadać mu ten sam generator sekretów i walidator co nowej ścieżce; wymusić PKCE S256 bez możliwości obniżenia zabezpieczeń, jednorazowy state, dokładną kontrolę schematu, hosta i ścieżki, jedną aktywną transakcję na przebieg oraz atomowe zużycie wywołania zwrotnego.


9. Dalsze ustalenia i obserwacje wymagające rozstrzygnięcia

9.1. MOB-O-01 — pełne URL w logowaniu zdalnym

Przechwytywacz dla części profili zewnętrznych przekazuje do zdalnego mechanizmu logowania pełny URL, potencjalnie wraz z parametrami zapytania. Konfiguracja produkcyjna włącza zdalne logowanie HTTP. Nie znaleziono jednak jeszcze potwierdzonego wywołania, w którym sekret lub dane osobowe przechodzą tą konkretną ścieżką. Jest to hipoteza do testu przepływowego, a nie gotowa podatność.

Zalecenie: globalnie usuwać parametry zapytania, fragment adresu, nagłówki autoryzacyjne i identyfikatory z telemetrii; utrzymywać listę dozwolonych pól diagnostycznych.

9.2. MOB-O-02 — szeroki zakres FileProvider

Dostawca treści nie jest eksportowany i wymaga jawnego nadania uprawnienia do URI, co jest poprawną granicą Androida: AndroidManifest.xml. Jego konfiguracja deklaruje jednak external-path path=".", czyli korzeń współdzielonej pamięci zewnętrznej, zamiast wydzielonego podkatalogu: provider_paths.xml. Sam wpis nie udostępnia plików bez przyznania uprawnienia i nie jest dowodem wycieku. Powoduje natomiast, że błąd w miejscu budującym URI lub nadającym czasowe uprawnienie może mieć znacznie większy zasięg niż potrzebny.

Zalecenie: zawęzić ścieżki dostawcy do wydzielonych katalogów i typów plików oraz zinwentaryzować wszystkie miejsca wywołujące getUriForFile, grantUriPermission i FLAG_GRANT_*_URI_PERMISSION. Test negatywny powinien potwierdzić, że nie da się utworzyć ani skutecznie nadać URI dla pliku spoza katalogu eksportu.

9.3. MOB-O-03 — zakres telemetrii Sentry i zdarzeń bezpieczeństwa

Produkcja inicjalizuje Sentry własnym kodem; automatyczna inicjalizacja manifestowa jest wyłączona. Konfiguracja nadaje zdarzeniom między innymi tagi:

  • wersja Google Play Services i poziom poprawek bezpieczeństwa;
  • wersje OpenGL i Vulkan, program uruchamiający i język aplikacji;
  • rozmiar czcionki, TalkBack, eksploracja dotykiem i animacje;
  • stan sieci komórkowej, Wi-Fi, GPS, NFC i biometrii systemowej;
  • X_SESSION_ID, poziom StrongBox, obciążenie CPU i wykryte zagrożenia.

Dowód: q00/d.java. Mechanizm logowania przyjmuje także komunikaty, wyjątki i ślady zdarzeń. Zestaw programistyczny (SDK) zawiera integracje pomiarowe dla uruchomienia aplikacji, Compose, baz danych, plików, Logcat i OkHttp. Sama obecność integracji w metadanych nie dowodzi, że każda jest włączona ani że przesyła treść danych. Biblioteka odtwarzania sesji (Session Replay) również znajduje się w pakiecie, lecz nie znaleziono konfiguracji aplikacyjnej potwierdzającej aktywne rejestrowanie sesji; nie przypisano go więc aplikacji.

Moduł wykrywania zagrożeń przekazuje do mechanizmu logowania zdarzenia dotyczące uprawnień root, emulatora i podatności BlueBorne oraz nazwę i identyfikator pakietu wykrytego złośliwego oprogramowania. Dowód: h83/c.java. Dane te mogą być uzasadnione ochroną przed nadużyciami, ale stanowią osobną granicę prywatności i powinny być objęte minimalizacją, retencją, kontrolą dostępu oraz jawną dokumentacją celu.

Nie znaleziono dowodu, że wrażliwa treść dokumentów jest wysyłana do Sentry. Jednocześnie przechwytywacz opisany w MOB-O-01 zapisuje pełny URL, dlatego brak wycieku musi zostać potwierdzony testem ruchu i zdarzeń, a nie założony na podstawie intencji implementacji.

9.4. MOB-O-04 — ochrona ekranów potwierdzenia przed atakiem przez zasłanianie interfejsu

Pełne przeszukanie źródeł i manifestu nie znalazło filterTouchesWhenObscured, setFilterTouchesWhenObscured, obsługi FLAG_WINDOW_IS_PARTIALLY_OBSCURED, Window.setHideOverlayWindows() ani deklaracji android.permission.HIDE_OVERLAY_WINDOWS. Produkcyjne FLAG_SECURE ogranicza przechwytywanie obrazu, ale nie jest kontrolą przyjmowania dotyku przez zasłonięte okno. Oficjalne zalecenie Androida rozróżnia pełne i częściowe zasłonięcie: nowszy system ogranicza część pełnych nakładek, lecz dla częściowego zasłonięcia nie ma ochrony domyślnej. Android Developers — ataki przez zasłanianie interfejsu (Tapjacking).

W aplikacji zawierającej potwierdzenia związane z tożsamością i podpisem jest to brak obrony warstwowej. Analiza statyczna nie dowodzi jednak, że nakładka potrafi uruchomić konkretną operację bez czytelnej zgody, biometrii, PIN-u lub walidacji serwera. Dlatego MOB-O-04 pozostaje obserwacją do testu, a nie stwierdzoną podatnością.

Zalecenie: dla całego okna czynności wrażliwych na Androidzie 12+ zadeklarować HIDE_OVERLAY_WINDOWS i użyć setHideOverlayWindows(true); niezależnie od wersji odrzucać dotknięcia oznaczone pełnym lub częściowym zasłonięciem na przyciskach potwierdzenia. Testy muszą objąć pełną nakładkę, przezroczystą nakładkę poniżej progu systemowego, częściowe zasłonięcie i umieszczenie aktywności między oknami napastnika (activity sandwich), z kontrolą dostępności dla prawidłowych narzędzi asystujących.

9.5. MOB-A-10 — nienormalizowana nazwa zdalnego załącznika w ścieżce Downloads na API 26–28

Ocena: Niskie–Średnie, warunkowe
Atakujący: nadawca lub inny podmiot mogący wpłynąć na nazwę pobieranego załącznika; alternatywnie przejęte zaplecze serwerowe
Warunki: Android 8–9 (API 26–28), zgoda na zapis w pamięci zewnętrznej oraz zaakceptowanie przez usługę nazwy zawierającej segmenty ścieżki
Pewność: potwierdzony brak walidacji po stronie klienta; zdalna sterowalność nazwy i skuteczność wobec produkcyjnej usługi wymagają testu

Łańcuch zaczyna się w odpowiedzi HTTP. Parser Content-Disposition wybiera filename= albo dekoduje filename*=, lecz nie usuwa separatorów katalogów, .. ani znaków sterujących: q10/a.java. Repozytorium e-Doręczeń przekazuje wynik bez zmiany do FileDto, a następnie DomainFile; w razie braku nagłówka używa bezpiecznej nazwy domyślnej: wn0/d.java, un0/a.java. Wspólny przypadek użycia rozdziela nazwę wyłącznie wokół ostatniej kropki i nie przeprowadza normalizacji ścieżki: ww3/l.java.

Standardowy przebieg JADX pominął główną metodę zapisu w ww3/m.java; ponowna dekompilacja tej samej klasy z --show-bad-code odtworzyła jawny warunek Build.VERSION.SDK_INT >= 29. Na API 29+ aplikacja używa MediaStore z relative_path=Downloads: r20/e.java. Na API 26–28 — które nadal mieszczą się w deklarowanym minSdkVersion=26 — buduje natomiast ścieżkę tekstową i otwiera ją przez FileOutputStream: ww3/m.java, AndroidManifest.xml, r20/e.java. Bazą jest publiczny katalog Downloads: r20/b.java. Funkcja składa go operatorem tekstowym jako Downloads + "/" + fileName + "." + extension, bez getCanonicalFile() i sprawdzenia prefiksu: ww3/c.java.

Przykładowa nazwa ../Pictures/probe.pdf prowadzi więc w starszej gałęzi do ścieżki równoważnej Download/../Pictures/probe.pdf. Manifest żąda WRITE_EXTERNAL_STORAGE, a przypadek użycia poprzedza zapis prośbą o to uprawnienie: AndroidManifest.xml, ww3/l.java. Kod sprawdza istnienie celu i dodaje przyrostek (n), zatem w przeanalizowanym przebiegu nie potwierdzono nadpisywania istniejącego pliku. Potwierdzona własność ogranicza się do możliwości utworzenia nowego pliku w innym istniejącym katalogu pamięci współdzielonej, w granicach uprawnień starego Androida. Nie oznacza zapisu do prywatnego obszaru innej aplikacji ani wykonania kodu.

flowchart LR HTTP[Odpowiedź HTTP] --> CD[Content-Disposition
filename / filename*] CD --> NAME[Nazwa bez walidacji
separatorów i ..] NAME --> SPLIT[Podział przy ostatniej kropce] SPLIT --> SDK{API urządzenia} SDK -->|29+| MS[MediaStore
relative_path = Downloads] SDK -->|26-28| PATH[Downloads / nazwa . ext] PATH --> FOS[FileOutputStream] PATH -.-> OUT[Segmenty .. kierują nowy plik
poza Downloads w pamięci współdzielonej] style NAME fill:#ffe0e0,stroke:#ad3030 style PATH fill:#ffe0e0,stroke:#ad3030 style MS fill:#d9f2e6,stroke:#16784b style OUT fill:#fff1c7,stroke:#a66a00

Test P1: na emulatorach API 26 i 28 wysłać w kontrolowanym środowisku załączniki o nazwach zwykłych oraz ../Pictures/probe.pdf, ..%2FPictures%2Fprobe.pdf w filename*=, z wieloma segmentami ../, separatorem odwrotnym i nazwą już istniejącą. Należy zapisać zarówno odpowiedź serwera, jak i rzeczywistą ścieżkę pliku. Test rozstrzyga, czy serwer zachowuje nazwę nadawcy, normalizuje ją lub odrzuca przed zwróceniem klientowi. Na API 29+ trzeba osobno potwierdzić zachowanie _display_name; nie należy automatycznie przenosić wyniku ze starej gałęzi.

Naprawa: potraktować nazwę z sieci wyłącznie jako etykietę. Odrzucać /, \\, segmenty ./.., NUL, znaki sterujące i nieakceptowane rozszerzenia; ograniczyć długość i zastosować listę dozwolonych znaków. Dla gałęzi plikowej obliczyć kanoniczny katalog bazowy i cel, a następnie wymagać, by cel pozostawał jego potomkiem. Najlepiej usunąć starszą gałąź bezpośredniego budowania ścieżki wraz z końcem wsparcia API 26–28 albo użyć bezpiecznego API systemowego również tam. Walidację trzeba wykonać niezależnie po stronie serwera i klienta.


10. Kontrola regresji wcześniejszego incydentu

CERT Polska opisał CVE-2025-11598 jako błąd wersji iOS starszych niż 4.71.0, w których dane mogły pozostać widoczne w systemowym przełączniku aplikacji po zakończeniu sesji. NVD klasyfikuje problem jako CWE-359 i niski poziom CVSS 4. Nie jest to CVE Androida i nie wolno przedstawiać statycznego sprawdzenia Androida jako potwierdzenia naprawy błędu iOS.

W Androidzie 4.87.2 znaleziono jednak właściwy wzorzec ochronny:

  • SECURE_WINDOW ma wartość produkcyjną true;
  • obserwator cyklu życia aktywności dodaje flagę o wartości 8192, czyli WindowManager.LayoutParams.FLAG_SECURE;
  • mechanizm jest dołączony do głównej aktywności.

Dowód: a04/c.java, y10/x.java.

Jest to dobra implementacja statyczna. Nie zastępuje testu listy ostatnich aplikacji, zrzutów ekranu i nagrywania obrazu na urządzeniach oraz wersjach Androida, zwłaszcza przez cały cykl: aktywna sesja → tło → wylogowanie → wymuszone zamknięcie → ponowne uruchomienie.


11. Magazyn kluczy — stan rzeczywisty i korekta wstępnych ustaleń

Potwierdzone dobre praktyki:

  • użycie AndroidKeyStore;
  • AES-GCM i operacje RSA w odpowiednich ścieżkach;
  • możliwość związania klucza z uwierzytelnieniem urządzenia lub biometrią;
  • możliwość unieważnienia po zmianie biometrii;
  • żądanie StrongBox dla wybranych kluczy tożsamości;
  • inspekcja faktycznego KeyInfo.securityLevel i raportowanie poziomu.

Korekty:

  • pole ksRandomizer jest przekazywane do setRandomizedEncryptionRequired(). Nie losuje aliasu klucza;
  • StrongBox.PREFERRED celowo dopuszcza ponowienie generacji bez StrongBox po ProviderException;
  • odczyt rzeczywistego poziomu zabezpieczenia jest widoczny, ale nie znaleziono ogólnej blokady funkcji przy poziomie SOFTWARE;
  • obecność klas AndroidX Security i Tink w APK nie dowodzi, że konkretna baza dokumentów jest nimi chroniona;
  • programowe wykonanie zastępcze nie jest automatycznie CWE-326. To decyzja o poziomie zapewnienia, którą należy zestawić z wymaganiami protokołu i modelem urządzeń.

Zalecenie: formalnie określić, które klucze wymagają co najmniej TEE, które StrongBox, a które mogą działać programowo. Warunek powinien być egzekwowany w punkcie emisji/użycia klucza, jeżeli bezpieczeństwo protokołu faktycznie od tego zależy.


12. Plan testów dynamicznych przed zamknięciem audytu

flowchart LR FIX[Korekta kodu] --> BUILD[Podpisana kompilacja produkcyjna] BUILD --> STATIC{Brama statyczna} STATIC -->|brak TrustAll/proceed
HTTPS + świeżość CT| DYNAMIC{Brama dynamiczna} STATIC -->|nie| REJECT[Odrzucenie wydania] DYNAMIC -->|P0 zaliczone| TRACE[Pakiet dowodowy
skrót + logi + urządzenia] DYNAMIC -->|nie| REJECT TRACE --> REVIEW[Powtórna niezależna rewizja] REVIEW -->|ustalenia zamknięte| ACCEPT[Akceptacja konkretnego artefaktu] REVIEW -->|otwarte ryzyko| REJECT style ACCEPT fill:#d9f2e6,stroke:#16784b style REJECT fill:#ffe0e0,stroke:#ad3030
Priorytet Test Kryterium pozytywne
P0 Ponowne podanie historycznej, prawidłowo podpisanej listy CT przez kontrolowany punkt dostępowy lub serwer pośredniczący aplikacja odrzuca cofnięcie wersji lub oznacza listę jako przeterminowaną
P0 Lista CT z błędnym podpisem i uszkodzony ZIP brak zastąpienia ostatniej znanej poprawnej wersji; brak pętli awarii; alert telemetryczny bez danych użytkownika
P0 Zrzut ekranu, nagrywanie obrazu i ekran ostatnich aplikacji na Androidzie 8, 14 i 16 brak obrazu dokumentu i danych na każdym etapie sesji oraz po wylogowaniu
P0 Kompilacja produkcyjna przy błędzie certyfikatu API/WebView połączenie zawsze odrzucone; brak przejścia po błędzie SSL
P0 CMS: zły podpis, brak podpisujących, obcy certyfikat, zły OID/typ, obca koperta z nieufnym CMS oraz wstrzyknięcie na wejściu odrzucenie przed zapisem i przed utworzeniem modelu interfejsu użytkownika
P0 KDF pakietu studenckiego: dwa kontrolowane pakiety, polityka kodu i pomiar słownika poza aplikacją unikalna losowa sól każdego pakietu; udokumentowana entropia, TTL i koszt ataku; brak praktycznej walidacji zgadywanego kodu poza aplikacją
P0 Pełny cykl klucza UNIVERSITY: kontrolowany pakiet, odzyskanie poza aplikacją, import PKCS#12, zapis bazy, użycie i unieważnienie brak transportu eksportowalnego klucza; klucz nieeksportowalny w magazynie kluczy; serwer odrzuca klucz po unieważnieniu; brak sekretów w stercie i logach
P0 OAuth e-Doręczeń w starszym y12.t: dwie równoległe sesje, obcy kod, brak lub powtórzenie state, obniżenie ochrony PKCE i wywołanie zwrotne o obcym hoście lub ścieżce starszy wariant usunięty albo każde wywołanie zwrotne związane z właściwą transakcją przez PKCE S256 i jednorazowy state; obce wartości odrzucone przed punktem końcowym tokenów
P1 Złośliwa aplikacja rejestrująca mobywatel:// brak ujawnienia użytecznego tokenu; przebieg odporny na przechwycenie i spreparowanie danych
P1 Jawny Intent do MainActivity z błędnym hostem, ścieżką lub tokenem odrzucenie przed zmianą stanu; brak awarii i wycieku diagnostycznego
P1 WebView usług internetowych: file://, content://, przekierowania, systemowy wybór pliku i złośliwy HTML allowFileAccess=false; nawigacja spoza listy dozwolonej zablokowana; brak odczytu plików i nadmiernych uprawnień do URI
P1 Nazwy załączników e-Doręczeń z ../, separatorami i zakodowanym filename*= na API 26, 28 i 29 nazwa odrzucona lub sprowadzona do bezpiecznej etykiety; cel kanoniczny zawsze pozostaje w Downloads
P1 Inspekcja Sentry przez kontrolowane punkty końcowe URL i zdarzenia nie zawierają tokenów, numeru PESEL, danych dokumentu ani parametrów zapytania
P1 Macierz urządzeń bez TEE / TEE / StrongBox poziom klucza zgodny z wymaganiami i czytelna polityka przejścia na wariant zastępczy
P1 Atak przez zasłanianie ekranów logowania, zgody i podpisu: pełna lub częściowa nakładka oraz aktywność między oknami napastnika dotyk w zasłoniętym oknie odrzucony; brak wykonania operacji; poprawne narzędzie dostępności nadal działa zgodnie z polityką
P2 Nadawanie URI przez FileProvider uprawnienie obejmuje wyłącznie oczekiwany plik i wygasa po użyciu

Testy powinny być wykonane na dokładnie tym samym certyfikowanym artefakcie wydania, dla którego zostaną opublikowane skrót kryptograficzny i versionCode. Wyniki negatywne także należy zachować w raporcie — brak powodzenia określonej hipotezy jest istotnym wynikiem audytu.


13. Macierz rewizji tez wstępnego przeglądu

Nagłówek pierwotnego pliku przypisuje autorstwo nazwie „Antigravity Research”. Nie jest to autor ani podmiot niniejszego audytu. Prawidłowe autorstwo raportu rewizyjnego: Marek Wesołowski (WESMAR), 2026.

Teza pierwotna Werdykt Uzasadnienie
APK jest podpisany oficjalnym kluczem Potwierdzone po uzupełnieniu dowodu Odcisk certyfikatu odpowiada oficjalnemu assetlinks.json; sam DN Google był niewystarczający.
HTTP dla gstatic pozwala wstrzykiwać czcionki/JS/zasoby Obalone Jedyny aktywny odbiorca to podpisana lista CT; błędny podpis jest odrzucany.
HTTP dla gstatic jest poprawne i bez konsekwencji Obalone Możliwe jest podstawienie starszej, poprawnie podpisanej listy; brak kontroli świeżości i stanu.
sslEnabled można przełączyć przez produkcyjny ekran deweloperski Obalone dla standardowej produkcji Kod interfejsu istnieje, ale produkcyjny moduł zarządzający jest niedostępny i brak eksportowanego wejścia.
Modyfikacja prywatnego XML przez root wyłącza TLS Technicznie prawdziwe, ale mylący model zagrożeń Uprawnienia root lub Frida oznaczają już kontrolę nad procesem; nie jest to zdalne ryzyko wysokie.
Obecność TrustAll w produkcji jest obojętna Obalone To niepotrzebna ścieżka akceptacji mimo błędu i ryzyko regresji, choć bez wykazanego zwykłego sposobu jej uruchomienia.
Przypinanie certyfikatów stanowi osobną średnią podatność Nieudowodnione / duplikat Wyłącza się w tej samej uprzywilejowanej ścieżce flagi co TLS.
ksRandomizer losuje alias Obalone Steruje setRandomizedEncryptionRequired().
Aplikacja blokuje krytyczne funkcje bez StrongBox/TEE Nieudowodnione Znaleziono zamierzone przejście PREFERRED na wariant zastępczy i raportowanie poziomu, a nie ogólną blokadę.
AndroidX/Tink chroni krytyczną bazę dokumentów Nieudowodnione Obecność biblioteki nie dowodzi konkretnego przepływu danych.
autoVerify należy dodać do mobywatel:// Technicznie błędne zalecenie Android App Links weryfikuje domenowe linki HTTP/HTTPS.
Odnośniki kierujące do aplikacji nie wymagają dalszych testów Obalone Własny schemat URI z tokenem wymaga testu kolizji, spreparowania danych i walidacji po stronie serwera.
Analiza była „pełna” Obalone opisem zakresu Nie wykonano testów dynamicznych, zaplecza serwerowego ani pełnej analizy kodu natywnego; źródła publiczne są niekompletne.

14. Dobre wzorce, które należy odnotować

Uczciwy audyt obejmuje również kontrole działające poprawnie:

  • android:allowBackup="false" i reguły tworzenia kopii oraz ekstrakcji wykluczające dane aplikacji;
  • globalne android:usesCleartextTraffic="false", z jednym jawnie opisanym wyjątkiem;
  • ochrona Safe Browsing dla WebView włączona w manifeście;
  • poprawna kryptograficzna weryfikacja listy CT i ograniczenia rozmiaru elementów ZIP;
  • zachowanie ostatniej znanej poprawnej wersji zamiast przyjęcia danych o błędnym podpisie;
  • pin podstawowy i zapasowy dla głównego API;
  • FLAG_SECURE domyślnie aktywne w produkcji;
  • brak aplikacyjnego addJavascriptInterface w przejrzanym kodzie;
  • restrykcyjne ustawienia bazowe WebView (allowFileAccess=false, zablokowana treść mieszana) oraz twarde sslEnabled=true w ekranach płatności;
  • produkcyjny odbiornik komunikatów debug/info nie utrwala treści pełnego URL ani DOM budowanego przez wspólny WebView; nie znaleziono zdalnej wysyłki tych komunikatów;
  • nowszy moduł autoryzacyjny implementuje transakcyjny state oraz PKCE S256 i odrzuca wywołanie zwrotne z niezgodnym state;
  • niemutowalne PendingIntent we własnych powiadomieniach i sprawdzonych przebiegach interfejsu; odziedziczona funkcja pomocnicza rejestracji Google tworzy osobny mutowalny token do fikcyjnego pakietu, dlatego nie zaliczono całej powierzchni bezwarunkowo do dobrych wzorców;
  • komponenty bibliotek eksportowane tylko z właściwymi uprawnieniami systemowymi;
  • FileProvider nieeksportowany;
  • użycie Android Keystore, żądanie StrongBox dla wybranych kluczy i inspekcja realnego poziomu zabezpieczenia.

Te elementy zmniejszają ryzyko, ale nie unieważniają ustaleń MOB-A-01, MOB-A-02 ani rozbieżności między wariantami opisanej jako MOB-A-09.


15. Odtwarzalność kluczowych sprawdzeń

Przykładowe polecenia:

sha256sum mObywatel.apkm decompiling/base.apk

apksigner verify --verbose --print-certs decompiling/base.apk

curl --fail --silent --show-error \
  https://api.mobywatel.gov.pl/.well-known/assetlinks.json

rg -n 'http://[^" ]+' src/resources src/sources

rg -n 'getVersion\(|getLogListTimestamp\(' src/sources

rg -n 'filterTouchesWhenObscured|setFilterTouchesWhenObscured|\
FLAG_WINDOW_IS_PARTIALLY_OBSCURED|setHideOverlayWindows|HIDE_OVERLAY_WINDOWS' \
  src/sources src/resources/AndroidManifest.xml

jadx --single-class iy3.c \
  --single-class-output /tmp/iy3_c_fallback.java \
  --decompilation-mode fallback decompiling/base.apk

unzip -p log_list.zip log_list.json > log_list.json
unzip -p log_list.zip log_list.sig > log_list.sig
openssl dgst -sha256 -verify gstatic_public_key.pem \
  -signature log_list.sig log_list.json

Należy zapisać wersje narzędzi, pełne dane wyjściowe, czas UTC pobrania zasobu zdalnego oraz skróty kryptograficzne wszystkich plików dowodowych. Plik z kluczem publicznym w ostatnim poleceniu powinien być zbudowany bezpośrednio z wartości gstatic_public_key badanego APK.


16. Łańcuch dostaw i podatności zależności

16.1. Inwentaryzacja

Metadane Sentry w APK zawierają listę 324 dokładnych współrzędnych Maven. Jest to użyteczny substytut wykazu składników oprogramowania (SBOM), lecz nie pełny dokument CycloneDX/SPDX: nie podaje relacji bezpośrednia/przechodnia, zakresu użycia, skrótów plików JAR ani modułów własnych. Dowód: sentry-external-modules.txt.

Wśród składników odnotowano między innymi:

Składnik Wersja w APK Znaczenie audytowe
Bouncy Castle bcprov / bcpkix / bcutil-jdk15to18 1.83 aktywnie używany dostawca kryptograficzny, CMS, PKCS#12 i ASN.1; znane CVE
Tink Android 1.8.0 stara wersja nie dowodzi użycia w krytycznym przepływie
Conscrypt 2.5.3 Java/JNI TLS; wymaga osobnej kontroli kodu natywnego
SQLCipher Android 4.10.0 natywna baza; obecność nie dowodzi zakresu szyfrowanych danych
OkHttp 4.12.0 oraz 2.7.2 dwa pokolenia API zwiększają koszt utrzymania
Sentry Android 8.22.0 telemetria kodu Java i natywnego
RootBeer 0.1.2 wykrywanie uprawnień root/JNI; mechanizm ten nie stanowi granicy zaufania
Protobuf 4.29.3 parser danych binarnych

Lista zawiera 28 artefaktów alpha/beta, głównie Compose 1.11.0-beta02, Material3 1.5.0-alpha18, Navigation3 1.1.0-alpha01 i ODML Image beta. Wersja przedprodukcyjna nie jest podatnością sama w sobie. W aplikacji państwowej powinna jednak wymagać jawnego wyjątku ryzyka, testów regresji i terminu przejścia na wydanie stabilne.

16.2. MOB-A-04 — Bouncy Castle 1.83

Zapytania do OSV dla wszystkich 324 współrzędnych, wykonane 25 sierpnia 2026 r., zwróciły trzy wpisy dotyczące dwóch z trzech spakowanych modułów Bouncy Castle:

Moduł Komunikat / CVE Mechanizm Wersja naprawiona Osiągalność kodu w aplikacji
bcpkix-jdk15to18:1.83 GHSA-wg6q-6289-32hp / CVE-2026-5588 roboczy CompositeVerifier akceptuje pustą sekwencję podpisów 1.84 klasa obecna; brak wywołania spoza BC
bcprov-jdk15to18:1.83 GHSA-574f-3g2m-x479 / CVE-2025-14813 licznik GOST 28147/G3413 CTR zawija się i powtarza strumień klucza 1.84 klasa obecna; brak użycia GOST spoza BC
bcprov-jdk15to18:1.83 GHSA-c3fc-8qff-9hwx / CVE-2026-0636 wstrzyknięcie danych w LDAPStoreHelper 1.84 klasa obecna; brak konfiguracji/wywołania LDAP spoza BC

Ręczna kontrola oficjalnej noty 1.84 wykazała, że pojedyncze źródło danych było niepełne. Projekt źródłowy wymienia także CVE-2026-3505 oraz CVE-2026-5598:

  • CVE-2026-3505 dotyczy OpenPGP AEAD; moduł bcpg i pakiet org.bouncycastle.openpgp nie występują w badanym APK, więc ten wpis nie jest przypisywany aplikacji;
  • CVE-2026-5598 dotyczy różnic czasowych przy dekapsulacji FrodoKEM. Kod Frodo znajduje się w bcprov, lecz nie znaleziono referencji aplikacyjnej do Frodo/FRODO/frodokem; osiągalność nie została wykazana.

Przeszukanie 45 452 zdekompilowanych plików Java wykazało symbole GOST, LDAP, Composite Signature i Frodo wyłącznie pod org/bouncycastle/**. Jest to negatywny wynik osiągalności kodu na poziomie jawnych odwołań, a nie matematyczny dowód nieosiągalności: dostawca JCA może być wybierany nazwą algorytmu, refleksją albo przez dane wejściowe.

Nie wolno natomiast uznać całej zależności za martwą. Aplikacja tworzy i rejestruje BouncyCastleProvider, generuje oraz odczytuje CMS, używa magazynu PKCS12 dostawcy BC i bezpośrednio odczytuje ASN.1. Przykłady: y10/d0.java, kh0/m.java, dh2/a.java, ez/e0.java. Oznacza to, że dla błędów CMS/PKIX/PKCS#12/ASN.1 sama obecność aktywnego odbiorcy jest potwierdzona, a rozstrzygnięcie musi zejść do konkretnych metod i danych wejściowych.

Bazowa ocena CVE opisuje najgorsze użycie wadliwego komponentu; nie jest automatycznie oceną badanej aplikacji. W szczególności krytyczna ocena CVE-2025-14813 wymaga rzeczywistego użycia trybu GOST CTR z warunkami ponownego użycia strumienia; takiego przebiegu nie wykazano. Z drugiej strony pozostawienie znanego podatnego dostawcy kryptograficznego w aplikacji tożsamościowej jest nieakceptowalnym długiem, ponieważ przyszłe lub słabo widoczne miejsce użycia może uczynić kod osiągalnym.

Wersja 1.84, która naprawiała powyższe problemy, została wydana 28 kwietnia 2026 r. Badana aplikacja trafiła do dystrybucji około 23–29 lipca 2026 r. Pozostawienie 1.83 nie może więc zostać wyjaśnione tym, że poprawka pojawiła się dopiero po zamknięciu kompilacji. To co najmniej niewystarczająca dyscyplina aktualizacji komponentu kryptograficznego albo brak wykazanego procesu akceptacji ryzyka.

Projekt źródłowy opublikował następnie 28 lipca 2026 r. Bouncy Castle 1.85, którego nota wymienia 32 dalsze poprawki CVE w całym produkcie, a 7 sierpnia poprawki dostawcy 1.85.2. Nie oznacza to, że wszystkie wpisy dotyczą użytych modułów albo przebiegów mObywatela. Ponieważ aplikacja używa CMS, PKCS#12 i ASN.1, priorytetowego rozpoznania wymagają między innymi klasy błędów CMS, walidacji RSA/certyfikatów, limitów ASN.1 i kosztu KDF. Nota wydania Bouncy Castle 1.85, bieżące pliki i noty wydania.

Zalecenie: przejść na najnowszy zgodny zestaw stabilnych artefaktów po testach współdziałania; usunąć nieużywane moduły i algorytmy przez minimalizację; generować podpisany SBOM dla każdego wydania; uruchamiać analizę składu oprogramowania (SCA) przy budowie i ponownie codziennie dla już wydanych wersji; łączyć co najmniej OSV/GHSA, NVD i komunikaty producenta; dla każdego CVE zapisywać decyzję „osiągalny / nieosiągalny / nierozstrzygnięty” wraz z dowodem. Samo podbicie numeru wersji bez testów nie zamyka ustalenia.

16.3. MOB-A-05 — treść CMS przyjmowana i odczytywana bez lokalnej weryfikacji podpisu

W wielu ścieżkach aplikacja tworzy CMSSignedData z danych binarnych i natychmiast pobiera getSignedContent().getContent(). Globalne wyszukanie nie znalazło poza kodem biblioteki wywołań getSignerInfos(), SignerInformation.verify(), verifySignatures() ani budowy SignerInformationVerifier. Najprostszy dekoder domenowy wykonuje wyłącznie:

return UTF_8.decode(ByteBuffer.wrap(
    (byte[]) new CMSSignedData(byteArray).getSignedContent().getContent()
)).toString();

Dowód: y10/f0.java. Ten dekoder zasila repozytoria dokumentów. Analogiczny wzorzec występuje w ih2/a.java oraz starszym magazynie storage/g.java. Wprost odczytywane w ten sposób są między innymi zakresy prawa jazdy i legitymacji studenckiej: d64/l.java, d64/l.java. Inna ścieżka najpierw odszyfrowuje CMSEnvelopedData kluczem użytkownika, a następnie tylko odczytuje wewnętrzne obiekty CMSSignedData: d64/l.java.

Brak nie ogranicza się do ścieżki odczytu. Publiczna metoda repozytorium b0(...) przekazuje otrzymany ciąg do K1(...); zależnie od trybu B0(...) albo A0(...) odszyfrowuje CMS EnvelopedData, tworzy wewnętrzne CMSSignedData, a następnie zapisuje ich getEncoded() do kontenera. Sprawdzana jest liczba elementów albo dozwolona nazwa zakresu (scope), lecz nie podmiot podpisujący. Dowody: d64/l.java, d64/l.java, d64/l.java. Analiza statyczna oznacza tu brak weryfikacji zarówno na zidentyfikowanej granicy przyjęcia danych, jak i później przy budowaniu modeli. Nie przesądza natomiast, kto może wywołać tę granicę z kontrolowaną treścią.

Osobny, istotny wariant dotyczy aktywacji legitymacji studenckiej. Pakiet pochodzi z punktu końcowego FrontSrv/rs/post/getPackage: uo0/b.java, yo0/b.java. Flaga MOB_DB_CONTAINERS, której wartości domyślne w artefakcie wynoszą false/false, przełącza całą implementację aktywacji pomiędzy ey3.a i wy3.a: a04/c.java, u54/y5.java. W starszej ścieżce packageData jest dekodowane i odszyfrowywane hasłem, po czym A1(...) wyciąga fullStudentData jako CMSSignedData i porównuje PESEL bez weryfikacji podpisu: d64/l.java, d64/l.java. Alternatywna implementacja również tworzy obiekty CMSSignedData z mapy pakietu bez znalezionej kontroli podpisujących: ey3/b.java. Flaga zmienia więc zaplecze magazynu i wykonanie aktywacji, ale w zbadanych wariantach nie usuwa omawianego braku.

CMSSignedData jest parserem i kontenerem; konstruktor ani getSignedContent() nie ustanawiają zaufania do podpisu. Dokumentacja projektu źródłowego pokazuje osobne pobranie getSignerInfos() i wywołanie signer.verify(...); zaznacza też, że nawet taki prosty przykład nie sprawdza jeszcze ważności certyfikatu. Bouncy Castle API — CMSSignedData. Prawidłowa walidacja wymaga co najmniej obecności oczekiwanej liczby podpisujących, weryfikacji kryptograficznej, budowy i polityki łańcucha certyfikatów, EKU/OID polityki, czasu, unieważnienia oraz związania podpisującego z typem dokumentu i środowiskiem.

Potwierdzone: klient przyjmuje i zapisuje w zidentyfikowanych przebiegach oraz odczytuje treść oznaczoną jako CMS SignedData bez znalezionej lokalnej weryfikacji podpisujących. Globalny wynik negatywny obejmuje również brak getSignerInfos(), SignerInformation.verify(), verifySignatures() i budowy SignerInformationVerifier poza biblioteką.

Niepotwierdzone: zwykły napastnik może samodzielnie dostarczyć taki kontener do repozytorium. Dane są chronione przez izolację aplikacji, TLS i w części przebiegów CMS EnvelopedData. Ta koperta kryptograficzna używa AES256_CBC i certyfikatu odbiorcy: bh2/a.java, dh2/a.java. Zapewnia poufność, lecz nie uwierzytelnia nadawcy ani nie jest trybem AEAD. Podmiot, który pozyska publiczny certyfikat odbiorcy, może technicznie zbudować skierowaną do niego kopertę; APK nie dowodzi jednak, że podmiot nieufny może pozyskać właściwy certyfikat i dostarczyć wynik do b0(...).

Nie należy mieszać tego z zewnętrzną warstwą pakietu aktywacyjnego legitymacji: w starszej implementacji C0/G0 używa hasła i AES/CBC/PKCS5Padding, a kod dopina IV na końcu szyfrogramu i nie pokazuje znacznika uwierzytelniającego: d64/l.java, d64/l.java, ch2/a.java, ch2/c.java. Niezależny ślad w nowej implementacji prowadzi od packageData i hasła przez generator klucza PBE do odszyfrowania wariantem z IvParameterSpec, a następnie do utworzenia CMSSignedData: ey3/b.java, ey3/b.java, y10/k.java, ey3/b.java. Jest to drugi rodzaj mechanizmu poufności bez potwierdzonej integralności kryptograficznej, obecny w obu implementacjach aktywacji; praktyczna modyfikacja nadal wymaga spełnienia formatu, dopełnienia, znajomości hasła lub kontrolowanej zmiany szyfrogramu oraz warunków dostarczenia pakietu. Dlatego błąd może stać się istotny przy przejęciu kanału, błędzie serwera, podatnym imporcie lub zapisie lokalnym, lecz nie wykazano samodzielnego zdalnego wektora.

Parametry KDF zewnętrznego pakietu aktywacyjnego, w tym potwierdzoną stałą sól PBKDF2, wydzielono jako niezależne ustalenie MOB-A-06. Nie są one dowodem braku weryfikacji CMS i nie należy sztucznie sumować obu wad, choć w jednym przepływie mogą wpływać na ten sam pakiet danych.

Aplikacja ma ogólny weryfikator Signature.initVerify()/verify() w y10/c.java, ale znaleziony odbiorca używa go do podpisu listy dzienników CT: iy3/c.java. Nie jest to gotowa implementacja polityki CMS: nie buduje łańcucha ani nie sprawdza EKU/OID, czasu i unieważnienia. Pokazuje jednak niespójność architektoniczną: podpis zewnętrznego artefaktu CT jest jawnie weryfikowany, podczas gdy podpisane dane dokumentów są jedynie odczytywane.

flowchart LR DOC[Zaplecze dokumentów] --> TLS1[TLS i przypinanie certyfikatów] TLS1 --> ENV[Opcjonalny CMS EnvelopedData] ENV --> ING[Przyjęcie danych A0 / B0] STUD[FrontSrv getPackage] --> TLS2[TLS i przypinanie certyfikatów] TLS2 --> CBC[Pakiet hasłowy AES-CBC] CBC --> ING2[Aktywacja legitymacji] ING --> SD[CMSSignedData] ING2 --> SD SD --> STORE[Zapis getEncoded] STORE --> PARSE[getSignedContent] PARSE --> MODEL[Model dokumentu / interfejs] SIGNER[Weryfikacja podpisującego,
łańcucha, EKU, czasu] -. nieznaleziona na wejściu
ani odczycie .-> SD style SIGNER fill:#ffe0e0,stroke:#ad3030 style MODEL fill:#fff1c7,stroke:#a66a00

Test P0: w kontrolowanym środowisku sprawdzić na granicy przyjęcia danych i ponownie przy odczycie: (a) SignedData z uszkodzonym podpisem, (b) SignedData bez podpisujących, (c) podpis poprawny, ale wykonany nieufnym certyfikatem, (d) poprawny podpis niewłaściwego typu dokumentu/OID, (e) kopertę zbudowaną poza oczekiwanym producentem do publicznego certyfikatu testowego użytkownika i zawierającą jeden z nieufnych wariantów a–d oraz (f) bezpośrednie wstrzyknięcie wariantów a–e do b0(...) i pakietu aktywacyjnego. Należy osobno zmierzyć zachowanie obu stanów MOB_DB_CONTAINERS. Warianty a–f muszą zostać odrzucone przed zapisem i przed zbudowaniem modelu interfejsu użytkownika, a log ma wskazać konkretną przyczynę bez ujawnienia danych dokumentu. Koperta utworzona przez inny podmiot, ale przenosząca autentyczny, zaufany i świeży CMS, jest odrębnym przypadkiem ponownego podania danych i ustalania ich pochodzenia — sama koperta nie zawiera dowodu nadawcy.

Naprawa: jedna, centralna funkcja verifyThenDecode, wywoływana na granicy przyjęcia danych przed getEncoded() i zapisem; ponowna walidacja przy odczycie jako obrona warstwowa; brak publicznego API decode odczytującego niezweryfikowaną treść; testy negatywne dla braku podpisujących, złej sygnatury, obcego certyfikatu, złego OID i czasu; oddzielne typy UntrustedCmsBytes oraz VerifiedDocumentPayload. Dla nowych formatów należy zastąpić nieuwierzytelnione AES-CBC trybem AEAD lub poprawną konstrukcją „najpierw szyfrowanie, potem MAC” (encrypt-then-MAC). Nie należy jednak traktować tego jako zamiennika podpisu wystawcy dokumentu.

16.4. MOB-A-06 — globalna stała sól PBKDF2 w pakiecie aktywacyjnym legitymacji

Pakiet aktywacyjny legitymacji studenckiej jest szyfrowany kluczem wyprowadzanym z kodu aktywacyjnego. Śledzenie danych rozdziela dwa elementy przekazywane użytkownikowi: State.getActivationCode() jest argumentem hasłowym operacji deszyfrowania packageData, natomiast setupData.getQrCode() trafia osobno do operacji oznaczenia pakietu jako pobranego: z63/n.java, z63/n.java. Zasoby interfejsu również mówią osobno o kodzie QR i kodzie aktywacyjnym oraz deklarują ich jednorazowe użycie: strings.xml. Nie ma więc podstaw, aby nazywać hasło „kodem QR”; z APK nie odtworzono natomiast alfabetu, długości ani procesu losowania kodu aktywacyjnego.

Parametr 0 widoczny przy tworzeniu PBEKeySpec jest fałszywym tropem dekompilacyjnym. Wywołanie korzysta z maski argumentu domyślnego Kotlina, a konstruktor syntetyczny podstawia PKIFailureInfo.notAuthorized, czyli 65 536: lz/d.java, PKIFailureInfo.java. Generator przekazuje tę wartość do javax.crypto.spec.PBEKeySpec; algorytm to PBKDF2-HMAC-SHA256, a długość klucza wynosi 256 bitów: e20/g.java. Zarzut „zero iteracji” byłby nieprawdziwy.

Potwierdzonym problemem jest sól. saltStudentID to wpisana w kod, ośmiobajtowa stała {-73, 116, 33, -84, 127, -116, -18, -103} obecna w obu implementacjach: d64/p.java, ey3/b.java. Funkcja przedstawiona przez dekompilator jako łączenie trzech wartości wykonuje cykliczny XOR, a nie konkatenację ani KDF: hh2/a.java. Wszystkie trzy argumenty są identyczne, więc wynik algebraicznie pozostaje tą samą ośmiobajtową stałą: salt XOR salt XOR salt = salt. Nowa ścieżka przekazuje je jawnie w ey3/b.java; starsza ścieżka robi to przez funkcję pomocniczą w bh2/a.java, wywołaną z tym samym stałym argumentem trzykrotnie w d64/l.java. Nie należy zatem przedstawiać tej funkcji pomocniczej jako dowodu użycia losowej soli dla legitymacji studenckiej.

flowchart LR QR[Kod QR] --> CTRL[Oddzielna obsługa pobrania / statusu] CODE[Kod aktywacyjny użytkownika] --> KDF[PBKDF2-HMAC-SHA256
65 536 iteracji] CONST[Ta sama stała 8 B
w każdym pakiecie] --> XOR[XOR trzech
identycznych wartości] XOR --> KDF KDF --> KEY[Klucz AES-256] DATA[packageData] --> CBC[AES-CBC] KEY --> CBC CBC --> DOC[JSON / kontenery CMS] style CONST fill:#ffe0e0,stroke:#ad3030 style CODE fill:#fff1c7,stroke:#a66a00 style DOC fill:#e2ecff,stroke:#345da8

Skutek kryptograficzny jest precyzyjny: sól nie zapewnia rozdzielenia użytkowników ani pakietów, a identyczny kod aktywacyjny i te same parametry PBKDF2 prowadzą do identycznego klucza AES. Napastnik posiadający wiele szyfrogramów może amortyzować przygotowanie słownika między celami. Jednorazowość kodu ogranicza jego ponowne użycie w legalnym protokole, lecz sama nie usuwa ataku słownikowego poza aplikacją po pozyskaniu pakietu. AES-CBC z dopełnieniem i przewidywalnie ustrukturyzowanym wynikiem może dostarczyć sposobu rozpoznania poprawnego odgadnięcia, ale jego praktyczność — podobnie jak kanał pozyskania packageData — wymaga potwierdzenia laboratoryjnego. Nie wykazano odzyskania kodu ani danych użytkownika.

Liczba 65 536 iteracji jest znacznie niższa od współczesnego punktu odniesienia OWASP wynoszącego 600 000 dla PBKDF2-HMAC-SHA256. Jest to porównanie z zaleceniem dotyczącym przechowywania haseł; nie stanowi automatycznego dowodu naruszenia normy tego protokołu ani samodzielnej podatności. Właściwy koszt należy dobrać do entropii sekretu, wydajności urządzeń i modelu zagrożeń. OWASP Password Storage Cheat Sheet.

Ocena: Niskie–Średnie, warunkowe. Stała globalna sól i parametry KDF są potwierdzone statycznie. Podniesienie oceny wymaga wykazania, że kod ma małą przestrzeń, zaszyfrowany pakiet jest dostępny napastnikowi i istnieje wiarygodny sposób sprawdzania zgadywanego kodu poza aplikacją. Brak uwierzytelnienia AES-CBC z MOB-A-05 zwiększa wagę całego przebiegu, ale pozostaje odrębną własnością kryptograficzną i nie powinien być liczony drugi raz jako skutek stałej soli.

Test P0: pozyskać w autoryzowanym środowisku co najmniej dwa pakiety testowe dla różnych kont i kontrolowanego tego samego kodu, potwierdzić powtarzalność soli oraz wyprowadzonego klucza, udokumentować alfabet, długość, rozkład, sposób generowania, TTL, jednorazowość i kanał przekazania kodu, a następnie zmierzyć koszt kontrolowanego słownika poza aplikacją dla rzeczywistego formatu. Osobno sprawdzić, czy dopełnienie, parser JSON/CMS lub komunikaty błędów tworzą niezawodny sposób potwierdzenia odgadnięcia. Wyniki należy zachować także wtedy, gdy hipoteza praktycznego ataku zostanie obalona.

Naprawa: generować kryptograficznie losową, unikalną sól co najmniej 16-bajtową dla każdego pakietu i przechowywać ją jawnie w uwierzytelnionym nagłówku; przejść na AEAD; wersjonować parametry KDF oraz format i jawnie odrzucać przejście do słabszego wariantu ze stałą solą. Koszt KDF należy dobrać pomiarem na wspieranych urządzeniach i zapisać jako politykę aktualizowalną bez zmiany kodu. Jeżeli kod aktywacyjny ma małą entropię, sama większa liczba iteracji nie zastąpi projektu protokołu odpornego na zgadywanie poza aplikacją.

16.5. MOB-A-08 — eksportowalny klucz prywatny w pakiecie aktywacyjnym i bazie aplikacji

MOB-A-06 określa koszt zaatakowania zewnętrznej warstwy pakietu. Analiza tekstu jawnego po jej odszyfrowaniu rozstrzyga, co stanowi chroniony zasób. StudentPackageData zawiera trzy pola typu String: pkcs12Pass, pkcs12Data oraz signedDataList: StudentPackageData.java. Implementacja aktywacji dekoduje Base64 obu pierwszych pól, zamienia hasło na char[] i wywołuje moduł zarządzający PKCS#12 z aliasem mlWork: ey3/b.java. Moduł ten ładuje magazyn Bouncy Castle PKCS#12, pobiera certyfikat X.509 i odpowiadający mu PrivateKey, po czym zwraca CertKeyPair: y10/b.java.

Hasło PKCS#12 i chroniony nim magazyn są współlokowane w tym samym zaszyfrowanym obiekcie. Hasło może wykrywać błędne odszyfrowanie i chronić sam plik PKCS#12 po jego przypadkowym oddzieleniu, ale nie stanowi niezależnej granicy poufności wobec podmiotu, który odszyfruje cały pakiet. Taki podmiot otrzymuje jednocześnie pkcs12Data i pkcs12Pass. Ochroną sieciową pozostają TLS i przypinanie certyfikatów; raport nie twierdzi, że szyfrogram jest dostępny pasywnemu napastnikowi internetowemu. Na poziomie formatu aplikacyjnego zewnętrzną granicą jest jednak PBKDF2 ze stałą solą oraz AES-CBC bez widocznego znacznika AEAD/MAC. Wewnętrzna integralność PKCS#12 nie obejmuje osobnego pola signedDataList, którego obiekty CMS są następnie jedynie odczytywane, jak opisano w MOB-A-05.

Po imporcie klucz nie jest zachowywany jako nieeksportowalny uchwyt AndroidKeyStore. saveUserCertificateUC otrzymuje typ UNIVERSITY, pustą starą frazę oraz CertKeyPair: ey3/b.java. Repozytorium serializuje getPrivateKey().getEncoded() bezpośrednio do pola privateKey encji certyfikatu: xx3/a.java, CertificateEntity.java. Jest to eksportowalna reprezentacja PKCS#8, a nie alias nieeksportowalnego klucza sprzętowego.

Baza nie jest przy tym jawnym SQLite. Jest otwierana przez SQLCipher z kluczem przekazanym do SupportOpenHelperFactory: o20/c.java. Produkcyjny dostawca pobiera klucz z masterKeyProvider: p20/d.java, u54/j2.java. Materiał klucza głównego jest utrzymywany w pamięci procesu, a szersza ścieżka zarządzania kluczami obsługuje klucze AndroidKeyStore: v14/a.java, co1/z.java. Oznacza to sensowną warstwę ochrony przechowywanych danych przed prostym skopiowaniem pliku bazy. Nie zmienia jednak podstawowej właściwości: po otwarciu bazy aplikacja odtwarza pełne bajty prywatnego klucza, a ich poufność zależy od klucza SQLCipher i bezpieczeństwa procesu, zamiast od sprzętowo egzekwowanej nieeksportowalności samego klucza.

Klucz UNIVERSITY nie jest martwym artefaktem. Warstwa tożsamości pobiera właśnie ten CertKeyPair dla legitymacji studenckiej: u54/h2.java. Wspólna infrastruktura używa prywatnych kluczy CertKeyPair do podpisywania JWS oraz CMS i do odszyfrowywania kluczy danych: j01/a.java, m0.java, nw0/h.java. Analiza statyczna pozwala więc nazwać go aktywnym prywatnym kluczem certyfikatu tożsamości aplikacyjnej. Nie dowodzi natomiast, że jest to klucz kwalifikowanego podpisu elektronicznego ani że samodzielnie umożliwia wykonanie czynności prawnej; takiego sformułowania raport celowo nie używa.

flowchart LR TLS[TLS + przypinanie certyfikatów] --> PKG[packageData
AES-CBC] CODE[Kod aktywacyjny] --> KDF[PBKDF2
stała sól] KDF --> PKG PKG --> JSON[StudentPackageData] JSON --> P12[pkcs12Data] JSON --> PASS[pkcs12Pass] P12 --> LOAD[KeyStore.load] PASS --> LOAD LOAD --> PRIV[PrivateKey / CertKeyPair] PRIV --> RAW[getEncoded: PKCS#8] RAW --> DB[(SQLCipher)] DB --> USE[Odtworzenie klucza
JWS / CMS / odszyfrowanie] HW[AndroidKeyStore
nieeksportowalny uchwyt] -. nie zastosowano
do tego klucza .-> PRIV style KDF fill:#ffe0e0,stroke:#ad3030 style RAW fill:#ffe0e0,stroke:#ad3030 style DB fill:#d9f2e6,stroke:#16784b style HW fill:#fff1c7,stroke:#a66a00

Higiena pamięci i ryzyko diagnostyczne

W ey3/b.java nie znaleziono Arrays.fill ani równoważnego czyszczenia. Odszyfrowany JSON, pkcs12Data, pkcs12Pass, char[], klucz AES, bufory PKCS#12 i obiekt PrivateKey powstają jako kolejne kopie oraz są utrzymywane w polach stanu korutyny. Sekrety zapisane jako niemutowalne String nie mogą zostać deterministycznie wyzerowane przed pracą mechanizmu odśmiecania pamięci (GC). Nie jest to samodzielny zdalny wektor, ale zwiększa skutki zrzutu pamięci, narzędzia diagnostycznego, podatności procesu albo analizy urządzenia po aktywacji.

Dwa automatycznie wygenerowane toString() są szczególnie niebezpiecznym wzorcem: StudentPackageData.toString() wypisuje całe pkcs12Pass, pkcs12Data i signedDataList, a CertificateEntity.toString() wypisuje tablice certyfikatu i prywatnego klucza bajt po bajcie: StudentPackageData.java, CertificateEntity.java. Nie znaleziono bezpośredniego miejsca wywołania, które zapisywałoby te obiekty w logach w analizowanej ścieżce, dlatego raport klasyfikuje to jako potencjalny wyciek diagnostyczny, a nie potwierdzoną eksfiltrację do Sentry.

Wpływ, model ataku i ocena

Jeżeli napastnik uzyska szyfrogram packageData i rzeczywista polityka kodów umożliwi atak słownikowy poza aplikacją, poprawne odgadnięcie odsłoni nie tylko dane dokumentu, lecz również plik PKCS#12, jego hasło i eksportowalny klucz prywatny. Możliwe następstwa zależą od rozszerzeń certyfikatu, akceptowanych operacji, ważności, unieważnienia oraz kontroli serwera. Mogą obejmować odtwarzanie aplikacyjnych podpisów lub uwierzytelnień albo odszyfrowywanie danych przeznaczonych dla tego certyfikatu, ale nie zostały jeszcze wykazane na produkcyjnym zapleczu serwerowym.

Atak na kopię bazy ma inną granicę: sam plik SQLCipher nie wystarcza bez klucza bazy. Po zdobyciu klucza SQLCipher pełny PKCS#8 jest jednak odzyskiwalny, ponieważ aplikacja nie przechowuje wyłącznie aliasu sprzętowego. Uprawnienia root lub pełna kontrola procesu dają już szerokie możliwości i nie powinny sztucznie podnosić CVSS; właściwym pytaniem jest odporność na kradzież kopii, lukę w procesie, zrzut po odblokowaniu oraz zakres skutków po utracie jednego urządzenia.

Ocena: Średnie, warunkowo Wysokie po P0. Potwierdzone są współlokacja klucza i hasła, eksportowalna postać PKCS#8, brak deterministycznego czyszczenia oraz brak izolacji klucza w AndroidKeyStore. Ocena Wysoka wymaga jednoczesnego wykazania praktycznego odzyskania kodu poza aplikacją z dostępnego szyfrogramu i skutecznego użycia odzyskanego klucza wobec nadal akceptującego go systemu. Bez tych dowodów określenie „przejęcie podpisu” byłoby nadinterpretacją.

Test P0: w wydzielonym środowisku i na własnych danych testowych: (a) zachować wyłącznie szyfrogram pakietu i przeprowadzić pomiar rzeczywistej przestrzeni kodów; (b) po kontrolowanym odzyskaniu kodu wyodrębnić PKCS#12, zweryfikować hasło i zgodność klucza z certyfikatem; (c) ustalić KeyUsage, ExtendedKeyUsage, wystawcę, okres ważności i wszystkie protokoły akceptujące certyfikat; (d) wykonać nieszkodliwy test podpisu i odszyfrowania w środowisku testowym; (e) unieważnić certyfikat i potwierdzić odrzucenie starego klucza w każdym punkcie końcowym; (f) po aktywacji wykonać kontrolowany zrzut sterty oraz inspekcję logów/Sentry pod kątem obu DTO i buforów; (g) skopiować bazę przed i po odblokowaniu, wykazać granicę SQLCipher oraz potwierdzić odzyskiwalność PKCS#8 dopiero po zdobyciu klucza bazy. Wyniki muszą rozdzielać przejęcie zawartości pakietu, procesu i bazy.

Naprawa: preferowany projekt generuje parę kluczy na urządzeniu wewnątrz AndroidKeyStore, wysyła wyłącznie CSR lub klucz publiczny i przechowuje alias zamiast PKCS#8. Polityka powinna wymagać TEE albo StrongBox tam, gdzie model zagrożeń tego wymaga, z jawnym zachowaniem na urządzeniu niespełniającym wymagań. Jeżeli import klucza wystawionego zewnętrznie jest nieunikniony, należy natychmiast importować go do AndroidKeyStore z restrykcyjnym KeyProtection, zweryfikować rzeczywisty poziom zabezpieczenia i usuwać wszystkie kopie importowe; sam import nie gwarantuje sprzętowej ochrony na każdym urządzeniu. Pakiet trzeba chronić AEAD, losową solą dla każdego pakietu, wysoką entropią sekretu oraz związaniem z użytkownikiem, urządzeniem, sesją i wersją protokołu. Hasło PKCS#12 przesłane obok magazynu nie może być przedstawiane jako dodatkowa bariera. Typy zawierające sekrety nie mogą generować toString(), equals() ani hashCode() operujących na pełnym materiale; bufory należy ograniczyć do byte[]/char[], czyścić w finally, a rotację i natychmiastowe unieważnienie klucza traktować jako element procedury incydentowej.

16.6. Ograniczenia skanu zależności

Skan nie obejmował pełnej korelacji CVE dla bibliotek .so, usług Google dostarczanych poza APK, kodu zaplecza serwerowego ani komponentów własnych bez współrzędnych Maven. Bazy podatności są opóźnione i niekompletne. Wynik „zero trafień” nigdy nie byłby dowodem braku błędów; wynik „trafienie” nie jest jeszcze dowodem osiągalności.

17. Detekcja zagrożeń — rzeczywista konfiguracja i jej granice

APK zawiera rozbudowany model raportu SDK BotSense, który teoretycznie przewiduje dane urządzenia, sieci, lokalizacji, integralności i klienta. Analiza samego modelu prowadziłaby do fałszywego wniosku o szerokim zbieraniu danych. Konfiguracja aplikacyjna jest znacznie węższa:

  • wyłącza większość raportów urządzenia i usług;
  • pozostawia aktywny test emulacji;
  • uruchamia osobno RootBeer;
  • kwalifikuje poziom poprawki sprzed 1 września 2017 r. jako ryzyko BlueBorne;
  • przekazuje SDK literal testUserID i nie wskazuje w tej ścieżce zdalnego URL konfiguracji;
  • przekazuje wykryte wyniki do zdalnego loggera jako THREAT_DETECTED.

Dowody: h83/c.java, h83/c.java, h83/c.java.

Odrębnie od heurystyk RootBeer/BotSense w APK znajduje się rzeczywista ścieżka Play Integrity. Konfiguracja niesie projectNumber, klient generuje 16 losowych bajtów przez SecureRandom, koduje je jako wartość jednorazową (nonce) i wywołuje requestIntegrityToken; wynik oraz ta wartość trafiają do raportu: c74/i.java, w64/l4.java, w64/g4.java. Generator kluczy AndroidKeyStore obsługuje także setAttestationChallenge, ale wyłącznie dla ścieżki EC i tylko wtedy, gdy wyzwanie jest niepuste: e20/m.java. Są to poprawne mechanizmy obronne i należy je odnotować. Analiza statyczna klienta nie pokazuje jednak, jak serwer weryfikuje token, wartość jednorazową, wiek werdyktu, licencję i rozpoznanie urządzenia ani jakie decyzje od nich zależą. Obecność API nie może więc zostać przekształcona w ogólne zapewnienie integralności urządzenia.

RootBeer i BotSense zwiększają koszt prostych modyfikacji, ale nie powinny stanowić jedynej przesłanki autoryzacji. Lokalny klient jest kontrolowany przez właściciela urządzenia; decyzje o wydaniu dokumentu, podpisie lub zmianie stanu muszą opierać się na kryptograficznie związanej sesji, świeżości, poświadczeniu integralności urządzenia o jasno opisanej polityce i walidacji serwera. Wykrycie uprawnień root nie naprawia błędu autoryzacji, a ich niewykrycie nie może oznaczać zaufania absolutnego.

18. Dziennik hipotez, ślepych uliczek i korekt

Wzorem analiz SKCI i EFITool zachowano nie tylko wynik końcowy, ale również ścieżki, które wyglądały obiecująco i zostały odrzucone. Pozwala to odtworzyć rozumowanie i zapobiega ponownemu przedstawianiu obalonych tez jako odkryć.

Hipoteza początkowa Sprawdzenie Wynik Znaczenie
HTTP do gstatic daje dowolne wstrzyknięcie zasobu wyszukanie odbiorców, format ZIP, test podpisu fałsz w tej postaci treść jest podpisana; pozostała możliwość podstawienia starszej listy
Dekompilacja pokazuje akceptację złego podpisu CT drugi tryb JADX + eksperyment: klucz prawidłowy, nieprawidłowy i brak klucza artefakt dekompilatora błędny podpis jest odrzucany
sslEnabled jest zdalnym przełącznikiem manifest, eksporty, produkcyjne wstrzykiwanie zależności, SharedPreferences brak ścieżki zwykłego napastnika kod akceptujący połączenie mimo błędu nadal powinien zniknąć z wydania produkcyjnego
Uprawnienia root pozwalają zmienić prywatną flagę i „przełamać TLS” porównanie uprawnień atakującego mylący model zagrożeń uprawnienia root lub Frida oznaczają już kontrolę nad procesem
ksRandomizer losuje alias klucza śledzenie argumentu do Android Keystore fałsz to setRandomizedEncryptionRequired()
StrongBox jest bezwzględnie wymagany ścieżka PREFERRED i obsługa ProviderException fałsz dla polityki ogólnej istnieje świadome przejście na wariant zastępczy i pomiar poziomu
Tink/SQLCipher dowodzi ochrony konkretnej bazy referencje i miejsca użycia nieudowodnione komponent nie jest dowodem przepływu danych
Odtwarzanie sesji (Session Replay) jest aktywne, bo SDK jest w APK wyszukanie konfiguracji aplikacyjnej nieudowodnione nie przypisano funkcji bez konfiguracji
BotSense zbiera cały szeroki model raportu analiza konfiguratora klienta fałsz dla badanej ścieżki większość opcji wyłączona; aktywne są węższe kontrole
CVSS biblioteki jest oceną mObywatela SCA, komunikaty projektu źródłowego i wyszukanie miejsc wywołań fałsz metodologiczny kod wadliwych mechanizmów istnieje, lecz ich osiągalność wymaga osobnego dowodu
PBKDF2 wykonuje zero iteracji maska argumentu domyślnego i konstruktor syntetyczny lz/d artefakt dekompilacji rzeczywista wartość to 65 536; problemem jest stała sól i nieznana entropia kodu
Trzy składniki saltStudentID są konkatenowane lub zawierają losowość urządzenia implementacja hh2/a i oba miejsca wywołania fałsz funkcja wykonuje XOR, a trzy identyczne stałe redukują się do tej samej stałej
PKCS#12 z legitymacji zawiera klucz podpisu kwalifikowanego śledzenie typu UNIVERSITY, repozytorium certyfikatów i konsumentów CertKeyPair nieudowodnione potwierdzono aktywny klucz prywatny tożsamości aplikacyjnej; nie potwierdzono kwalifikowanego zastosowania prawnego
SQLCipher oznacza, że klucz prywatny jest nieeksportowalny format CertificateEntity i getPrivateKey().getEncoded() fałsz baza jest szyfrowana, ale po jej otwarciu aplikacja odtwarza pełny PKCS#8 zamiast używać wyłącznie aliasu magazynu kluczy
FLAG_SECURE rozwiązuje atak przez zasłanianie interfejsu porównanie semantyki flagi ze ścieżką obsługi zasłoniętych dotknięć fałsz flaga chroni obraz okna; nie zastępuje filterTouchesWhenObscured, obsługi częściowego zasłonięcia ani ukrycia nakładek
Sprawdzenie istnienia pliku przed zapisem tworzy możliwość nadpisania przez TOCTOU śledzenie generatora nazw i FileOutputStream niepotwierdzone jako istotny wektor potencjalny wyścig dotyczy pamięci współdzielonej i nie daje zapisu poza uprawnieniami; odrębnym błędem jest brak kanonikalizacji w MOB-A-10
Publiczny kod zawiera przykład isPies/isDog pełne rg w 192 plikach i dekompilacji nieodtworzone anegdoty bez pliku i identyfikatora zmiany nie użyto jako ustalenia

Ta tabela nie „broni” produktu. Oddziela to, co można zaatakować dowodem, od tego, co jedynie źle wygląda. Pozwala dzięki temu ostrzej sformułować potwierdzone zarzuty bez rozcieńczania ich błędami.

18.1. Macierz kompletności przeglądu klas zagrożeń

Poniższa macierz określa, co rzeczywiście sprawdzono w artefakcie. Wynik „brak potwierdzonego wektora” nie znaczy „niemożliwe”; oznacza, że przeszukanie miejsc wywołań nie wykazało przepływu z niezaufanego wejścia do niebezpiecznej operacji w dostępnej analizie statycznej.

Klasa zagrożenia Zakres kontroli Wynik w APK 4.87.2 Klasyfikacja / dalszy krok
Integralność CMS / pochodzenie dokumentu konstrukcja, przyjęcie, zapis i odczyt CMSSignedData; miejsca wywołań API weryfikacji brak znalezionej weryfikacji podpisujących w cyklu dokumentu MOB-A-05, P0
Kryptografia hasłowa i materiał klucza parametry PBKDF2, sól, AES, PKCS#12, zapis certyfikatu, higiena pamięci stała sól; klucz i hasło współlokowane; eksportowalny PKCS#8 MOB-A-06/A-08, P0
OAuth / odnośniki do aplikacji / App Links eksporty manifestu, schematy, walidacja wywołania zwrotnego, state, PKCE, warianty flag starszy wariant bez state/PKCE; własne schematy URI nie mają własności App Links MOB-A-03/A-09, P0
TLS, przypinanie certyfikatów, CT i WebView profile klientów, wartości produkcyjne, flagi, obsługa błędów, JS, dostęp do plików i treść mieszana dobre wartości domyślne, ale kod akceptujący połączenie mimo błędu pozostaje; CT bez kontroli świeżości MOB-A-01/A-02
Wyjście poza katalog / eksport plików Content-Disposition, nazwy DTO, MediaStore, bezpośrednie ścieżki, FileProvider i uprawnienia warunkowe wyjście poza katalog na API 26–28; dostawca prywatny, lecz zbyt szeroki MOB-A-10 / MOB-O-02
Niebezpieczna deserializacja Java ObjectInputStream, readObject, readSerializable, źródła danych WorkManager czyta typy prymitywne; Serializable ograniczone do wewnętrznych typów Parcel; brak niezaufanego readObject brak potwierdzonego wektora; testowanie mutacyjne parserów nadal wymagane
Deserializacja polimorficzna JSON adaptery Gson, metadane typów i miejsca wywołań nie znaleziono aplikacyjnego polimorficznego tworzenia dowolnych klas z niezaufanego JSON brak potwierdzonego wektora
Wstrzyknięcie SQL zapytania raw query/execSQL, konkatenacje i wiązanie argumentów Room/generowany SQL i parametry wiązane; trafienia surowe biblioteczne/literalne brak potwierdzonego wektora; serwer poza zakresem
Dynamiczne ładowanie kodu / wstrzyknięcie polecenia powłoki DexClassLoader/PathClassLoader, refleksja, Runtime.exec/ProcessBuilder mechanizm ładowania modułów GMS i stałe polecenia RootBeer; brak danych aplikacyjnych sterujących kodem lub komendą brak potwierdzonego wektora
Przejęcie PendingIntent wszystkie bezpośrednie PendingIntent.get*, flagi i cele własne akcje niemutowalne; jedna mutowalna funkcja pomocnicza GMS do nieuprzywilejowanego fikcyjnego pakietu dług biblioteczny, bez potwierdzonej podatności
Atak przez zasłanianie interfejsu flagi okna, obsługa zasłoniętego dotyku, ukrywanie nakładek, manifest FLAG_SECURE obecny, brak aplikacyjnej ochrony zasłoniętego dotyku MOB-O-04, test dynamiczny
Losowość bezpieczeństwa Random, SecureRandom, miejsca użycia IV, soli i tokenów trafienia Random dotyczą interfejsu, mediów i bibliotek; ścieżki bezpieczeństwa używają SecureRandom, poza stałą solą MOB-A-06 brak dodatkowego ustalenia
Logowanie sekretów / schowek zdalny mechanizm logowania, toString() DTO sekretów, miejsca użycia schowka brak potwierdzonego logowania wartości sekretów; ryzykowne toString() i pełne URL; schowek używany jawnie przez użytkownika MOB-O-01/O-03, test telemetrii
Kod natywny inwentaryzacja ABI, ELF NX/RELRO/BIND_NOW/ochrona stosu, eksporty wysokiego poziomu podstawowe utwardzenie ARM64 dobre; brak pełnej inżynierii wstecznej i testowania mutacyjnego uzupełnić dla 4 ABI i JNI
Uprawnienia root, instrumentacja i poświadczenie integralności RootBeer/BotSense, Play Integrity, poświadczenie klucza, punkty decyzji i model zaufania token z losową wartością jednorazową i wyzwaniem klucza są obecne; polityki walidacji serwera nie widać; klient po przejęciu lub instrumentacji pozostaje środowiskiem kontrolowanym granica modelu; serwer nie może ufać wyłącznie wynikowi klienta

19. Kod natywny i pozostała nieprzejrzysta powierzchnia

Pakiety dzielone dla arm64-v8a, armeabi-v7a, x86 i x86_64 zawierają po 11 bibliotek natywnych:

Grupa Biblioteki
TLS / kryptografia / dane libconscrypt_jni.so, libsqlcipher.so
Telemetria libsentry.so, libsentry-android.so
OCR / obraz / kody libmlkit_google_ocr_pipeline.so, libbarhopper_v3.so, libimage_processing_util_jni.so, libsurface_util_jni.so
AndroidX / DataStore libandroidx.graphics.path.so, libdatastore_shared_counter.so
Detekcja środowiska libtoolChecker.so

Kontrola nagłówków plików i nagłówków programu wszystkich 11 bibliotek w pakiecie ARM64 przyniosła wynik pozytywny:

Kontrola ELF Wynik ARM64 Interpretacja
typ ELF DYN 11/11 prawidłowy format współdzielonych obiektów pozycyjnie niezależnych; nie należy utożsamiać tego mechanicznie z testem PIE wykonywalnego pliku
stos bez prawa wykonania (GNU_STACK = RW, bez E) 11/11 NX dla stosu skonfigurowane poprawnie
GNU_RELRO oraz BIND_NOW 11/11 pełny RELRO we wszystkich badanych bibliotekach
odwołanie do __stack_chk_fail 10/11 ochrona stosu widoczna w bibliotekach zawierających kwalifikujące funkcje

Jedynym plikiem bez odwołania do __stack_chk_fail jest liczący około 10 KiB libdatastore_shared_counter.so. Eksportuje krótkie operacje JNI na liczniku, mmap i ftruncate; brak symbolu nie dowodzi globalnego wyłączenia ochrony, ponieważ kompilator wstawia ochronę stosu tylko do funkcji spełniających jego kryteria. Dlatego nie utworzono z tego pozornej podatności. Uczciwy wniosek brzmi: konfiguracja podstawowego utwardzenia ELF dla ARM64 jest dobra, ale sam zestaw flag nie dowodzi bezpieczeństwa logiki ani parserów.

Przegląd wszystkich bezpośrednich konstrukcji PendingIntent.get* również nie wykazał mutowalnej akcji należącej do aplikacji. Własne powiadomienia używają wartości 201326592, czyli FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT: FirebaseService.java, x54/c.java. Funkcja pomocnicza Google Play Services wybiera flagę niemutowalności dla przebiegu interfejsu: s5/r.java. Wyjątkiem jest odziedziczona funkcja pomocnicza rejestracji Google, która od Androida 12 tworzy mutowalny PendingIntent do pakietu com.google.example.invalidpackage: fg/c.java, wg/a.java. Jest on przekazywany jako identyfikator pochodzenia, lecz nie wskazuje uprzywilejowanej akcji aplikacji i w badanym śladzie nie daje odbiorcy kontroli nad funkcją mObywatela. Pozostaje długiem bibliotecznym: należy potwierdzić aktualność zależności i usunąć mechanizm, jeżeli współczesny protokół rejestracji go nie wymaga.

Obecny etap nie objął kompletnej inżynierii wstecznej, symbolicznego śledzenia wejść ani testowania mutacyjnego JNI. Szczególnie istotne są parsery obrazu i kodów, SQLCipher, Conscrypt oraz granice Java↔JNI, ponieważ błędy pamięci w kodzie natywnym mogą zmienić obserwację biblioteczną w wykonanie kodu lub eskalację wewnątrz procesu aplikacji.

Niezbędne uzupełnienie: skróty kryptograficzne każdej biblioteki dla każdej ABI, identyfikacja wersji i build-id, powtórzenie kontroli utwardzenia dla pozostałych ABI, kontrola CFI/PAC/BTI, mapowanie eksportów JNI, testowanie mutacyjne danych wejściowych i porównanie z komunikatami projektu źródłowego. Brak pełnej analizy kodu natywnego jest jawną luką zakresu, a nie przemilczanym zapewnieniem bezpieczeństwa.

20. Publiczny kod, licencja i niezależny demonstrator C++23/WinAPI

20.1. Co rzeczywiście opublikowano

Sprawdzona kopia publicznego repozytorium wskazywała zmianę d39d2f947b1078332d758de086cb3cb7dd163940 z 4 stycznia 2026 r. i zawierała 192 pliki Kotlin. Zakres odpowiada systemowi wzornictwa i interfejsowi użytkownika, a nie pełnej aplikacji. Pliki COI są objęte dołączoną licencją MIT; licencję kopii repozytorium i prawa do innych składników należy rozpatrywać oddzielnie. Licencja na kod nie udziela automatycznie prawa do znaków, nazwy, urzędowych symboli ani sugerowania autoryzacji państwowej.

Oficjalny komunikat z 29 grudnia 2025 r. mówi o publikacji „kodu źródłowego aplikacji”. Technicznie dostępny zakres nie umożliwia zbudowania aplikacji ani audytu warstw bezpieczeństwa. To uzasadnia precyzyjną krytykę braku przejrzystości, ale nie uprawnia do twierdzenia, że każda brakująca część jest wadliwa. Komunikat Ministerstwa Cyfryzacji.

20.2. Granice niezależnej implementacji

Poniższa część ma charakter techniczny. Ocenę prawną planowanego sposobu wykorzystania należy powierzyć prawnikowi właściwej specjalności.

Polska ustawa o prawie autorskim stanowi, że ochrona programu obejmuje formy wyrażenia, ale nie idee, zasady ani zasady będące podstawą interfejsów. Osoba uprawniona do korzystania z egzemplarza może obserwować, badać i testować jego działanie. Dekompilacja ma węższą podstawę: musi być konieczna do interoperacyjności niezależnie stworzonego programu i spełniać warunki art. 75 ust. 2 pkt 3. Uzyskanej informacji nie wolno używać do programu o istotnie podobnej formie wyrażenia ani innych naruszeń. Co istotne, ogólnego dozwolonego użytku osobistego z art. 23 nie stosuje się do programów komputerowych. Ustawa o prawie autorskim, art. 74–77.

Z tego wynika bezpieczniejszy profil prywatnego demonstratora:

  • własny kod C++23, architektura, układ ekranów, nazwa i grafika;
  • funkcjonalna inspiracja i interoperacyjność, bez kopiowania zdekompilowanych fragmentów;
  • własny wzór Windows 11: Mica, tryb ciemny i Per-Monitor V2 dodatkowo odróżniają formę wyrażenia;
  • wyłącznie fikcyjne dane i stałe oznaczenie DEMO — nie jest dokumentem ani aplikacją państwową;
  • brak oficjalnych kluczy, certyfikatów, kont produkcyjnych i podszywania się pod autoryzowanego klienta;
  • oddzielny dziennik pochodzenia wymagań: publiczna dokumentacja/obserwacja → neutralna specyfikacja → własna implementacja.

Niezależna aplikacja nie staje się przez podobną funkcjonalność aplikacją mObywatel ani dokumentem elektronicznym wywołującym skutki ustawowe. Przed dystrybucją, użyciem realnych protokołów lub danych i integracją z systemami państwowymi potrzebna jest pisemna autoryzacja oraz przegląd prawnika od prawa autorskiego, znaków i systemów teleinformatycznych.

20.3. Czy WinAPI oznacza mniejszą powierzchnię ataku

WinAPI i niewielka liczba zależności mogą zmniejszyć ryzyko łańcucha dostaw, rozmiar SBOM oraz liczbę parserów i środowisk wykonawczych. „Zero zależności” nie jest jednak celem bezpieczeństwa samym w sobie: Windows, biblioteki systemowe, sterowniki i kompilator nadal są zależnościami, a własny parser, kryptografia lub TLS prawdopodobnie zwiększą ryzyko.

flowchart TD UI[Własny interfejs Win32
Mica + tryb ciemny + PMv2] --> CORE[Mały rdzeń C++23
RAII + jawne typy] CORE --> NET[WinHTTP
TLS systemowy] CORE --> CRYPTO[CNG: BCrypt / NCrypt
bez własnej kryptografii] CORE --> SECRET[DPAPI / Windows Hello
sekrety związane z użytkownikiem] CORE --> DATA[Minimalny parser
limity rozmiaru i czasu] NET --> SERVICE[Wyłącznie środowisko DEMO
lub autoryzowany punkt końcowy] CRYPTO --> TRUST[Magazyn certyfikatów Windows] SECRET --> TRUST classDef own fill:#e2ecff,stroke:#345da8,color:#12213d; classDef system fill:#d9f2e6,stroke:#16784b,color:#102a20; classDef boundary fill:#fff1c7,stroke:#a66a00,color:#342300; class UI,CORE,DATA own; class NET,CRYPTO,SECRET,TRUST system; class SERVICE boundary;

Zalecana konfiguracja bazowa: /std:c++23, ostrzeżenia jako błędy, /permissive-, /sdl, /guard:cf, ASLR/DEP, CET tam, gdzie są dostępne, Control Flow Guard, podpisywanie binarki, analiza MSVC/clang-tidy, testowanie mutacyjne parserów, testy Application Verifier i brak sekretów w logach oraz małych zrzutach pamięci. WebView2 należy dodać tylko wtedy, gdy wymaganie rzeczywiście wymusza HTML; wnosi oddzielne, szybko aktualizowane środowisko wykonawcze.

20.4. Deklaracja autorska — kierunek niezależnej implementacji referencyjnej

Ustalenia tego raportu nie mają charakteru wyłącznie krytycznego; wyznaczają zarazem wzorzec, według którego zamierzam zbudować niezależną aplikację referencyjną o zbliżonej funkcjonalności. Przepływ danych i model działania znam na tyle dokładnie, by odtworzyć go samodzielnie w języku C++23, w oparciu o natywne WinAPI, bez pośrednictwa rozbudowanego stosu kilkuset bibliotek. Rezygnacja z tej powłoki nie jest celem samym w sobie, lecz świadomą decyzją inżynierską: każda zbędna zależność to dodatkowy parser, kolejne środowisko uruchomieniowe i wydłużenie łańcucha dostaw, a zatem realny przyrost powierzchni potencjalnego ataku. Rdzeń mniejszy i przejrzysty łatwiej poddaje się higienie kodu, przeglądowi oraz utrzymaniu. Wybór natywnego WinAPI nie zamyka przy tym drogi na inne systemy: ta sama zasada — niewielki rdzeń, minimum zależności i kryptografia powierzona zaudytowanym mechanizmom platformy — przenosi się na Linuksa z nowoczesnym, natywnym zestawem narzędzi (GTK4/libadwaita), czego dowodzi mój edytor rejestru RegEdLin zbudowany dokładnie w tym duchu.

Nie piszę tego z pozycji teoretyka. Pracuję na co dzień poniżej warstw, które niniejszy raport traktuje jako gwarancje: wymuszania podpisu sterowników, HVCI, zabezpieczeń opartych na wirtualizacji i Bezpiecznego Jądra, aż po fazę UEFI — mechanizmów, w które Microsoft inwestuje ogromne środki, a które w moich narzędziach badawczych (zob. github.com/wesmar) potrafię obejść lub odtworzyć na każdym poziomie: od modyfikacji g_CiOptions i przekierowania SeCiCallbacks, przez uzyskanie uprawnień SYSTEM jeszcze przed ekranem logowania — bez zapisu na dysk i bez sterownika jądra — po własny sterownik systemu plików NTFS działający w środowisku UEFI. Kto rozumie te warstwy od tej strony, inaczej rozkłada zaufanie w aplikacji tożsamości: nie rozważa, czy wykrywanie roota jest dostatecznie pomysłowe, lecz przyjmuje, że urządzenie bywa w pełni kontrolowane, i przenosi rozstrzygnięcia tam, dokąd klient nie sięga. Po trzydziestu latach tej praktyki architektura takiej aplikacji powstałaby zupełnie inaczej — nie dlatego, że dysponuję większą liczbą bibliotek, lecz dlatego, że mam ich znacznie mniej i wiem, której warstwy trzeba naprawdę pilnować.

Prace prowadzę w reżimie clean-room i na licencji MIT: własny kod, własna architektura, własny interfejs, własna nazwa i grafika — bez przejmowania znaków, symboli urzędowych czy sugerowania autoryzacji państwowej. Bezpieczeństwo opieram nie na zaufaniu do klienta, lecz na dyscyplinie protokołu: pojedynczej granicy „zweryfikuj, zanim odczytasz" dla treści podpisanej, uwierzytelnionym szyfrowaniu w miejsce trybów pozbawionych kontroli integralności, kluczach nieeksportowalnych osadzonych w sprzęcie zamiast materiału przenoszonego do klienta oraz serwerze rozstrzygającym o każdej istotnej decyzji. Świadomie nie piszę przy tym własnej kryptografii ani własnego TLS — sięgam po zweryfikowane mechanizmy systemowe, ponieważ to one, a nie sama liczba zależności, przesądzają o rzeczywistym poziomie zapewnienia.

Nie jest to zapowiedź „aplikacji doskonałej", lecz zobowiązanie do warsztatu, w którym opisane w tym raporcie słabości granicy zaufania nie mają prawa się pojawić.

21. Macierz korekt, odpowiedzialności i kryteriów zamknięcia

ID Właściciel Działanie Dowód zamknięcia Termin zalecany
MOB-A-01 sieć/PKI HTTPS, monotoniczna wersja i znacznik czasu, stan oraz przedział czasowy dziennika CT test starszej podpisanej listy kończy się odrzuceniem przed następnym wydaniem
MOB-A-02 platforma Android usunięcie TrustAll, permisywnego weryfikatora i proceed() z wydania produkcyjnego przeszukanie końcowego APK + test błędu certyfikatu przed następnym wydaniem
MOB-A-03 aplikacja mobilna / serwer migracja tokenów do App Links HTTPS, jednorazowość, TTL, stan i odbiorca test aplikacji kolidującej i spreparowanego Intentu 30 dni
MOB-A-04 bezpieczeństwo aplikacji / budowanie aktualizacja BC, pełny SBOM i osiągalność kodu dla wszystkich CVE 1.83→bieżące wynik SCA bez nierozstrzygniętego ryzyka Wysokiego/Krytycznego + testy podpisów/PKI natychmiast
MOB-A-05 kryptografia / dokumenty centralne verifyThenDecode przy przyjęciu i odczycie; typowanie danych niezaufanych i zweryfikowanych sześć wariantów negatywnych odrzuconych przed zapisem i interfejsem użytkownika w obu stanach flagi natychmiast / P0
MOB-A-06 kryptografia / aktywacja losowa sól dla każdego pakietu, AEAD, wersjonowanie parametrów KDF i ochrona przed przejściem do słabszego wariantu dwa pakiety nie współdzielą soli ani klucza; pomiar i test rozpoznawania poprawnej próby poza aplikacją; test migracji starego formatu natychmiast / P0
MOB-A-08 kryptografia / tożsamość / serwer generowanie lub bezpieczny import klucza do AndroidKeyStore, nieeksportowalność, usunięcie sekretów z DTO, rotacja i unieważnienie pełny test cyklu klucza; w bazie wyłącznie alias; brak PKCS#8 i hasła w stercie oraz logach; stary certyfikat odrzucony natychmiast / P0
MOB-A-09 aplikacja mobilna / OAuth / serwer wycofanie y12.t albo przeniesienie state + PKCE S256, ścisła walidacja zwrotnego URI i ochrona przed obniżeniem zabezpieczeń test dwóch sesji i wstrzyknięcia obcego kodu; każde niedopasowanie odrzucone przed wymianą tokenu natychmiast / P0
MOB-A-10 aplikacja mobilna / serwer / pliki oczyszczanie nazwy po obu stronach, kanoniczne związanie celu z katalogiem, usunięcie starszej ścieżki bezpośredniego zapisu testy nazw z ../, separatorami i filename*= na API 26/28/29; plik nigdy nie powstaje poza Downloads następne wydanie / P1
MOB-O-01/03 prywatność / telemetria centralne usuwanie wrażliwych części URL i danych, lista dozwolonych znaczników, retencja zapis zdarzeń z testu nie zawiera parametrów zapytania, tokenów ani danych osobowych przed zamknięciem audytu
MOB-O-02 platforma Android zawężenie FileProvider i audyt grantów test URI spoza katalogu kończy się odmową następny cykl utwardzenia
MOB-O-04 aplikacja mobilna / bezpieczeństwo interfejsu ochrona okna i kontrola zasłoniętych dotknięć na ekranach wrażliwych macierz nakładek nie wykonuje operacji i zachowuje wymaganą dostępność następny cykl utwardzenia
NATYWNE właściciele JNI wersje, utwardzenie, CVE i testowanie mutacyjne 11 bibliotek dla 4 ABI pakiet skrótów, build-id, wyniki utwardzenia i testów mutacyjnych przed zapewnieniem o bezpieczeństwie
ŹRÓDŁA właściciel produktu pełne źródła, plik blokady zależności, instrukcja odtworzenia, mapowanie zmiany źródłowej na APK odtworzona kompilacja i porównanie artefaktów po formalnym przekazaniu kodu
flowchart LR FIND[Ustalenie] --> OWNER[Jawny właściciel] OWNER --> PATCH[Korekta + test jednostkowy] PATCH --> REL[Finalny podpisany artefakt] REL --> NEG[Test negatywny / regresyjny] NEG -->|zaliczony| EVID[Skrót + log + środowisko] NEG -->|niezaliczony| PATCH EVID --> INDEP[Niezależna weryfikacja] INDEP -->|potwierdzona| CLOSED[Zamknięte] INDEP -->|brak dowodu| OWNER style CLOSED fill:#d9f2e6,stroke:#16784b style FIND fill:#ffe0e0,stroke:#ad3030

Ustalenie nie jest zamknięte komunikatem prasowym, zmianą w repozytorium ani deklaracją zespołu. Zamknięcie dotyczy konkretnego podpisanego artefaktu i wymaga dowodu testowego możliwego do niezależnego powtórzenia.

22. Konkluzja

Najistotniejszym potwierdzonym problemem wersji 4.87.2 nie jest dowolne wstrzyknięcie treści przez gstatic.com, lecz możliwość cofnięcia podpisanej listy zaufanych dzienników CT, ponieważ podpis chroni integralność i pochodzenie, ale implementacja nie chroni świeżości. Jest to precyzyjny, naprawialny błąd obrony warstwowej o warunkowym wpływie.

Niebezpieczne implementacje TrustAll, permisywnej weryfikacji nazwy hosta oraz SslErrorHandler.proceed() są obecne w artefakcie wydania i powinny z niego zniknąć. Dostępny materiał nie potwierdza jednak, aby zwykły napastnik mógł przełączyć produkcyjną aplikację na tę ścieżkę. Klasyfikowanie tego jako zdalnej podatności wysokiej wagi byłoby nierzetelne.

Przegląd WebView potwierdził jednocześnie, że nie wszystkie warianty są jednakowe. Płatności mają SSL wymuszone na stałe, bazowe ustawienia treści mieszanej i dostępu do plików są restrykcyjne, a nie znaleziono addJavascriptInterface; to należy pochwalić. Z drugiej strony usługi internetowe włączają allowFileAccess, a wspólny klient nadal zawiera proceed() oraz kod budujący pełny DOM i URL dla mechanizmu logowania. Badany produkcyjny odbiornik odrzuca poziomy debug/info, więc nie ogłoszono wycieku treści WebView. Jest to jednak konfiguracja krucha: bezpieczeństwo zależy od właściwego wariantu i konkretnej implementacji mechanizmu logowania, zamiast od niezmiennych ograniczeń komponentu.

Odrębny problem dotyczy starszej autoryzacji e-Doręczeń. y12.t rozpoczyna przebieg kodu autoryzacyjnego bez state i PKCE, akceptuje wywołanie zwrotne na podstawie schematu oraz początku ścieżki, a następnie wysyła code_verifier=null. Nowszy moduł obecny w APK implementuje state i PKCE S256, zatem luka wynika z niespójności między wariantami, a nie z braku możliwości technicznej. Ponieważ dostępność y12.t zależy od serwerowej flagi NATIVE_LOGIN_EDOR_MODULE_ANDROID, raport ocenia MOB-A-09 jako średnie i warunkowe oraz wymaga testu P0 zamiast bezpodstawnie deklarować aktywne przejęcie sesji.

W tej samej domenie funkcjonalnej znaleziono warunkowy błąd zapisu załączników na wspieranych nadal Androidach 8–9. Nazwa z Content-Disposition dociera bez normalizacji do ścieżki opartej na publicznym Downloads, a ../ może skierować nowy plik do innego katalogu pamięci współdzielonej. Kod nie nadpisuje istniejącego celu i gałąź Androida 10+ używa MediaStore, dlatego nie jest to uniwersalny dowolny zapis ani zdalne wykonanie kodu (RCE). Jest to jednak klasyczny brak egzekwowania granicy katalogu po stronie klienta, którego nie wolno usprawiedliwiać oczekiwanym oczyszczaniem danych na serwerze. MOB-A-10 wymaga testu P1 z nazwą kontrolowaną przez nadawcę.

Drugi obszar wymagający pilnego działania to łańcuch dostaw. Bouncy Castle 1.83 zawiera kod objęty znanymi CVE, a projekt źródłowy wydał już 1.84 i następnie linię 1.85.x z szerokim zestawem korekt. Nie znaleziono odwołań aplikacyjnych do GOST, LDAP, Composite Signature ani FrodoKEM, dlatego raport nie twierdzi, że tymi mechanizmami osiągnięto eskalację. Brak dowodu wykorzystania nie jest jednak argumentem za pozostawieniem podatnej wersji aktywnie używanego dostawcy kryptograficznego.

Jeszcze poważniejszego rozstrzygnięcia wymaga sposób wykorzystywania CMS. Analiza statyczna potwierdziła tworzenie i zapis CMSSignedData na wejściu oraz liczne odczyty getSignedContent() bez znalezionej lokalnej weryfikacji podpisujących; uzyskane dane zasilają modele dokumentów. Osobny pakiet aktywacyjny legitymacji przychodzi z FrontSrv, jest chroniony hasłowym AES-CBC bez widocznego znacznika uwierzytelniającego i w obu implementacjach prowadzi do konstrukcji CMSSignedData bez znalezionego verify. Nie wykazano jeszcze zwykłego zdalnego kanału dostarczenia podrobionego kontenera, dlatego nie ogłoszono przełamania integralności dokumentów. Jednak w produkcie tożsamościowym przyjmowanie obiektu nazwanego i zaprojektowanego jako SignedData bez jawnego verify wymaga natychmiastowego testu P0 i korekty, a nie kosmetycznego zalecenia.

W tym samym pakiecie aktywacyjnym potwierdzono niezależny błąd KDF: globalną, ośmiobajtową sól PBKDF2. Widoczne w dekompilacji zero iteracji jest artefaktem; rzeczywista wartość to 65 536, dlatego raport nie powiela efektownego, ale fałszywego zarzutu. Stała sól usuwa rozdzielenie kluczy między pakietami i umożliwia amortyzowanie słownika, lecz bez znajomości entropii kodu oraz kanału dostępu do szyfrogramu nie dowodzi jeszcze praktycznego odzyskania danych.

Analiza tekstu jawnego domknęła wpływ tego wątku: pakiet zawiera plik PKCS#12 z aktywnym prywatnym kluczem certyfikatu UNIVERSITY oraz hasło do tego pliku. Po aktywacji klucz jest zapisywany jako eksportowalny PKCS#8 w bazie SQLCipher, a nie jako nieeksportowalny uchwyt AndroidKeyStore. SQLCipher i magazyn kluczy zapewniają rzeczywistą ochronę przechowywanych danych i zostały w raporcie odnotowane; nie przywracają jednak nieeksportowalności samego klucza. Praktyczne odzyskanie poza aplikacją i użycie klucza pozostają testem P0, dlatego raport klasyfikuje MOB-A-08 jako Średnie, warunkowo Wysokie po potwierdzeniu, zamiast ogłaszać bez dowodu przejęcie podpisu kwalifikowanego.

Publiczne 192 pliki Kotlin nie obejmują warstw, które przesądzają o bezpieczeństwie. Dlatego obecny raport jest analizą binarną, a nie audytem źródłowym. Po otrzymaniu pełnego kodu należy powiązać wydanie z konkretną zmianą źródłową, odtworzyć kompilację, porównać artefakty i ponownie przejrzeć te same ustalenia na źródłach. Do czasu wykonania testów P0 nie należy wydawać zbiorczego zapewnienia o bezpieczeństwie ani alarmistycznego komunikatu o przełamaniu aplikacji.


23. Źródła zewnętrzne