Testy penetracyjne są jednym z ostatnich etapów praktycznej weryfikacji bezpieczeństwa aplikacji Java przed lub po wdrożeniu na produkcję. Pentestica zwraca uwagę na istotną różnicę między automatycznym wykrywaniem potencjalnych podatności przez SAST i DAST a sprawdzeniem, czy znalezione słabości lub błędy logiki rzeczywiście można wykorzystać w realnym scenariuszu ataku.
W nowoczesnym projekcie Java bezpieczeństwo nie powinno zaczynać się kilka dni przed wdrożeniem.
Analiza zależności, SAST, testy jednostkowe, kontrola sekretów, skanowanie kontenerów czy DAST mogą być częścią pipeline’u CI/CD znacznie wcześniej. Dzięki temu wiele problemów zostaje wykrytych jeszcze wtedy, gdy ich poprawienie jest stosunkowo proste.
Pozostaje jednak pytanie:
czy aplikacja, która przechodzi wszystkie automatyczne kontrole bezpieczeństwa, rzeczywiście jest bezpieczna?
Nie musi być.
Narzędzie potrafi znaleźć określony wzorzec w kodzie lub nietypową odpowiedź aplikacji. Znacznie trudniej jest mu zrozumieć, że użytkownik typu CUSTOMER może wykonać operację przewidzianą wyłącznie dla ADMIN, albo że odpowiednie połączenie kilku poprawnie działających endpointów pozwala ominąć ograniczenie biznesowe.
Właśnie w tym miejscu automatyzację zaczyna uzupełniać manualna analiza bezpieczeństwa.
Bezpieczeństwo aplikacji Java zaczyna się przed napisaniem pierwszego endpointu
W typowej aplikacji opartej na Spring Boot można wdrożyć wiele mechanizmów bezpieczeństwa już na etapie developmentu.
Zespół może między innymi:
- kontrolować zależności Maven lub Gradle,
- analizować kod statycznie,
- wykrywać sekrety trafiające przypadkiem do repozytorium,
- sprawdzać konfigurację kontenerów,
- wykonywać testy bezpieczeństwa w CI/CD,
- analizować obrazy Dockera,
- testować endpointy REST,
- monitorować podatności wykorzystywanych bibliotek.
To bardzo dobra podstawa.
Nie powinna jednak prowadzić do przekonania, że wszystkie problemy można sprowadzić do jednej listy CVE albo wyniku skanera.
Duża część rzeczywistego bezpieczeństwa aplikacji wynika bowiem z tego, jak własny kod łączy uwierzytelnianie, autoryzację, dane oraz procesy biznesowe.
SAST odpowiada na inne pytanie niż pentest
Static Application Security Testing analizuje aplikację bez wykonywania jej w taki sposób jak użytkownik końcowy.
SAST może pomóc znaleźć między innymi niebezpieczne konstrukcje w kodzie, niewłaściwą obsługę danych wejściowych czy fragmenty wymagające dokładniejszej analizy.
Jego ogromną zaletą jest możliwość uruchamiania bardzo wcześnie.
Developer może otrzymać informację o potencjalnym problemie jeszcze przed merge’em pull requestu.
To jednak również źródło ograniczenia.
Analiza kodu nie zawsze posiada pełną wiedzę o tym, jak aplikacja będzie wykorzystywana po wdrożeniu.
Wyobraźmy sobie aplikację Java obsługującą klientów firmy.
Mamy endpoint:
GET /api/customers/{customerId}/invoices
Kod nie zawiera SQL Injection.
Token JWT jest prawidłowo weryfikowany.
Endpoint wymaga zalogowania.
SAST może więc nie znaleźć niczego szczególnie podejrzanego.
Problem zaczyna się, jeśli backend nie sprawdza, czy customerId z adresu rzeczywiście należy do użytkownika określonego w tokenie.
Wtedy zalogowany klient może zmienić identyfikator:
/customers/1042/invoices
na:
/customers/1043/invoices
i otrzymać dane innej osoby.
Nie mamy tutaj klasycznej sytuacji, w której aplikacja „nie ma zabezpieczeń”.
Zabezpieczenia istnieją.
Błąd znajduje się w logice ich zastosowania.
DAST widzi uruchomioną aplikację, ale nadal nie zna całego kontekstu
Dynamic Application Security Testing działa z innej perspektywy.
Narzędzie analizuje działającą aplikację, wysyła żądania i obserwuje odpowiedzi.
Może dzięki temu wykrywać problemy niewidoczne podczas samej analizy kodu.
To istotna warstwa bezpieczeństwa, szczególnie przed wdrożeniem lub na środowisku możliwie zbliżonym do produkcyjnego.
Nadal pozostaje jednak zasadniczy problem:
automat nie zna biznesowego znaczenia każdej operacji.
Załóżmy, że aplikacja posiada proces:
utworzenie zamówienia → wybór płatności → autoryzacja → potwierdzenie → realizacja
Każdy endpoint może działać zgodnie ze specyfikacją.
Co jednak stanie się, gdy tester spróbuje:
- pominąć jeden etap,
- wywołać etapy w innej kolejności,
- zmienić kwotę po autoryzacji,
- użyć tokenu wygenerowanego dla innego obiektu,
- ponownie wykorzystać wcześniejsze żądanie,
- wykonać operację równolegle,
- zmienić rolę lub identyfikator w odpowiednim miejscu?
To właśnie takie scenariusze często wymagają zrozumienia działania całego systemu.
Gdzie zaczynają się testy penetracyjne?
SAST i DAST odpowiadają przede wszystkim na pytania o potencjalne nieprawidłowości.
Pentester próbuje pójść krok dalej.
Jeżeli aplikacja posiada określoną słabość, sprawdza:
czy można ją wykorzystać i dokąd prowadzi?
To podejście stosuje również Pentestica, analizując bezpieczeństwo aplikacji webowych, API i pozostałych elementów środowiska IT.
Dobry pentest aplikacji Java nie polega więc na ręcznym powtórzeniu wyników skanera.
Tester może próbować łączyć różne informacje i zachowania systemu w jeden scenariusz.
Przykładowo:
- API ujawnia identyfikatory obiektów.
- Jeden endpoint niepoprawnie sprawdza właściciela obiektu.
- Inna funkcja pozwala wykonać operację na wskazanym identyfikatorze.
- System posiada kilka poziomów uprawnień.
- Połączenie zachowań umożliwia dostęp do danych niedostępnych dla danej roli.
Każdy element z osobna może wyglądać mało interesująco.
Połączone mogą stworzyć rzeczywistą ścieżkę ataku.
Spring Security nie zabezpieczy błędnej logiki biznesowej
Spring Security daje developerom rozbudowany zestaw mechanizmów uwierzytelniania i kontroli dostępu.
Możemy ograniczać endpointy rolami, definiować reguły bezpieczeństwa czy zabezpieczać metody.
To jednak framework.
Nie zna znaczenia biznesowego naszych danych.
Przykładowo:
@GetMapping("/orders/{id}")
public OrderDto getOrder(@PathVariable Long id) {
return orderService.findById(id);
}Endpoint może być dostępny wyłącznie dla zalogowanych użytkowników.
To jeszcze nie oznacza, że jest odpowiednio zabezpieczony.
Gdzie następuje sprawdzenie, czy pobierane zamówienie należy do aktualnie zalogowanego klienta?
Jeżeli takiej kontroli nie ma, aplikacja może posiadać błąd autoryzacji, mimo że sam mechanizm logowania działa dokładnie tak, jak zaplanowano.
Developer powinien myśleć nie tylko:
„Czy ten użytkownik jest zalogowany?”
ale również:
„Czy ten użytkownik może wykonać tę konkretną operację na tym konkretnym obiekcie?”
Ta różnica ma ogromne znaczenie.
API zasługuje na oddzielne testowanie
W architekturze współczesnych aplikacji Java API jest często ważniejsze od samego interfejsu webowego.
Z jednego backendu może korzystać:
- aplikacja webowa,
- aplikacja mobilna,
- panel administratora,
- system partnera,
- mikroserwisy,
- integracje zewnętrzne.
Frontend może ukrywać określone funkcje, ale napastnik nie musi z niego korzystać.
Może wysyłać żądania bezpośrednio do API.
Dlatego podczas testów warto sprawdzić między innymi:
- autoryzację każdego endpointu,
- kontrolę dostępu do obiektów,
- role użytkowników,
- tokeny JWT i ich zakres,
- mechanizmy odświeżania tokenów,
- nieudokumentowane endpointy,
- stare wersje API,
- rate limiting,
- możliwość masowego pobierania danych,
- nadmiarowe dane w odpowiedziach,
- zachowanie przy nietypowych wartościach parametrów.
Szczególnie istotne jest testowanie więcej niż jednego konta.
Jeżeli system posiada klienta A i klienta B, tester powinien próbować wykonać operacje na danych B przy użyciu uprawnień A.
W przypadku rozbudowanych aplikacji należy również sprawdzać przejścia pomiędzy rolami.
Microservices nie rozwiązują problemu autoryzacji
Architektura mikroserwisowa pozwala rozdzielać odpowiedzialności, ale jednocześnie zwiększa liczbę komunikujących się elementów.
W projekcie mogą pojawić się:
- API Gateway,
- serwer autoryzacyjny,
- kilka usług Spring Boot,
- Kafka lub RabbitMQ,
- Redis,
- zewnętrzne API,
- systemy przechowywania danych,
- Kubernetes.
Każda granica pomiędzy usługami staje się kolejnym miejscem, w którym trzeba podjąć decyzję dotyczącą zaufania.
Czy mikroserwis może ufać danym otrzymanym od innej usługi?
Czy każda usługa ponownie sprawdza uprawnienia?
Czy wewnętrzny endpoint rzeczywiście jest dostępny wyłącznie z sieci wewnętrznej?
Czy nagłówek zawierający informacje o użytkowniku może zostać przesłany bezpośrednio przez klienta?
Czy API Gateway jest jedynym miejscem odpowiedzialnym za autoryzację?
To pytania architektoniczne, których nie da się rozwiązać jednym skanerem.
JWT: poprawny podpis to dopiero początek
Token JWT może być kryptograficznie poprawny i jednocześnie używany w aplikacji w niebezpieczny sposób.
Podczas analizy warto sprawdzić między innymi:
- termin ważności,
- sposób odświeżania,
- unieważnianie tokenów,
- zakres uprawnień,
- weryfikację
issuer, - weryfikację
audience, - sposób przechowywania tokenu po stronie klienta,
- możliwość użycia tokenu w innym systemie,
- zaufanie do danych znajdujących się w claims.
Samo użycie standardu nie gwarantuje poprawnego modelu autoryzacji.
Zależności Java są kolejną warstwą ryzyka
Ekosystem Java opiera się w dużym stopniu na bibliotekach zewnętrznych.
Jest to jedna z jego największych zalet.
Nie ma sensu implementować samodzielnie wszystkiego od początku.
Oznacza to jednak, że aplikacja dziedziczy również ryzyko związane ze swoimi zależnościami.
Dlatego pipeline powinien uwzględniać analizę używanych komponentów i ich wersji.
Samo wykrycie podatnej biblioteki również nie kończy analizy.
Trzeba odpowiedzieć na kolejne pytania:
- czy podatny kod jest rzeczywiście używany?
- czy jest osiągalny z perspektywy napastnika?
- czy istnieją warunki wymagane do wykorzystania podatności?
- czy konfiguracja aplikacji ogranicza ryzyko?
- czy problem można połączyć z inną słabością?
Automatyczny wynik powinien więc być początkiem procesu decyzyjnego, a nie jego końcem.
SAST + SCA + DAST + pentest zamiast wyboru jednego narzędzia
Nie ma większego sensu pytać:
„Co jest lepsze — SAST, DAST czy pentest?”
Każda metoda widzi system z innej strony.
SAST
Daje informację wcześnie i działa blisko kodu.
SCA
Pomaga kontrolować komponenty i zależności.
DAST
Analizuje zachowanie uruchomionej aplikacji.
Test penetracyjny
Próbuje połączyć techniczne i biznesowe zachowanie systemu w praktyczny scenariusz ataku.
Najbardziej sensowny proces bezpieczeństwa wykorzystuje więc warstwy.
Automatyzujemy wszystko, co da się rozsądnie automatyzować, a manualną pracę przeznaczamy na obszary wymagające rozumowania, kontekstu oraz kreatywności.
Szerzej taki model można rozłożyć na konkretne bramki bezpieczeństwa w całym cyklu wytwarzania oprogramowania.
Kiedy aplikacja Java powinna przejść pentest?
Nie każda zmiana wymaga pełnego zewnętrznego testu.
Warto jednak rozważyć go szczególnie wtedy, gdy:
- uruchamiana jest nowa aplikacja,
- powstaje nowe API,
- zmienia się system uwierzytelniania,
- przebudowywany jest model uprawnień,
- dodawane są płatności,
- aplikacja zaczyna przetwarzać bardziej wrażliwe dane,
- następuje istotna zmiana architektury,
- system zostaje udostępniony nowej grupie użytkowników,
- pojawia się duża integracja z zewnętrznym systemem,
- aplikacja od dawna nie była niezależnie testowana.
Znaczenie ma nie tylko liczba zmian.
Istotniejsze jest to, jakiego elementu bezpieczeństwa dotyczą.
Mała zmiana w kodzie odpowiedzialnym za autoryzację może mieć większe znaczenie niż tysiące nowych linii kodu w funkcji raportowej.
Jak przygotować projekt Java do testu penetracyjnego?
Dobre przygotowanie pomaga wykorzystać czas pentestera na rzeczywistą analizę zamiast na odtwarzanie podstawowych informacji o systemie.
Warto przygotować:
1. Zakres
Domeny, subdomeny, aplikacje i endpointy objęte badaniem.
2. Konta dla różnych ról
Jeżeli istnieją role:
- USER,
- PREMIUM_USER,
- MODERATOR,
- ADMIN,
tester powinien mieć możliwość sprawdzenia granic pomiędzy nimi.
3. Dwa konta w tej samej roli
Pozwala to testować dostęp użytkownika A do zasobów użytkownika B.
4. Dokumentację API
OpenAPI/Swagger lub kolekcja Postman znacząco ułatwia pokrycie endpointów.
5. Informacje o ograniczeniach
Należy ustalić, których działań nie wolno wykonywać oraz czy test odbywa się na produkcji, stagingu czy specjalnym środowisku.
6. Kontakt awaryjny
W przypadku wykrycia podatności krytycznej wykonawca powinien wiedzieć, z kim skontaktować się natychmiast.
7. Informacje o WAF i innych mechanizmach ochronnych
W przeciwnym razie część testu może sprowadzić się do badania zachowania WAF zamiast samej aplikacji.
Co powinien dostać zespół developerski?
Rezultatem dobrego testu nie powinna być lista typu:
HIGH – endpoint podatny.
Taka informacja jest dla developera mało użyteczna.
Raport powinien pozwolić odpowiedzieć na pytania:
- gdzie znajduje się problem,
- jak go odtworzyć,
- jakie warunki są potrzebne,
- co uzyskał tester,
- jakie jest rzeczywiste ryzyko,
- jaką klasę problemu reprezentuje,
- jakie rozwiązanie należy rozważyć.
Dla zespołu Java szczególnie wartościowy jest scenariusz reprodukcji.
Jeżeli developer może odtworzyć błąd w środowisku testowym, znacznie łatwiej:
- zrozumieć jego przyczynę,
- wprowadzić poprawkę,
- dodać test regresyjny,
- upewnić się, że podobny błąd nie wróci.
Retest zamyka cykl
Naprawa podatności nie zawsze oznacza jej rzeczywiste usunięcie.
Developer może poprawić konkretną ścieżkę, pozostawiając alternatywny sposób wykonania tej samej operacji.
Dlatego po wdrożeniu zmian warto ponownie sprawdzić znalezione problemy.
W przypadku profesjonalnych testów penetracyjnych retest pozwala zweryfikować, czy podatność została rzeczywiście usunięta, a nie tylko zmieniła formę.
Dla zespołu developerskiego to również cenna informacja zwrotna.
Jeżeli poprawka nie działa, można przeanalizować dlaczego i ulepszyć sposób projektowania podobnych zabezpieczeń w kolejnych funkcjach.
Pentest nie powinien być egzaminem dla programisty
Warto na koniec zmienić sposób myślenia o zewnętrznych testach bezpieczeństwa.
Pentester nie przychodzi po to, aby udowodnić developerom, że napisali „zły kod”.
Jego rolą jest spojrzenie na system z zupełnie innej perspektywy.
Developer najczęściej myśli:
„Jak użytkownik powinien korzystać z tej funkcji?”
Pentester pyta:
„Co się stanie, jeśli użyję jej inaczej niż przewidział autor?”
Obie perspektywy są potrzebne.
Dobrze zaprojektowany proces pozwala wykorzystać automatyczne kontrole podczas każdej zmiany, a manualną analizę wtedy, gdy jej wartość jest największa.
SAST, SCA i DAST pomagają ograniczyć dużą część powtarzalnej pracy.
Test penetracyjny sprawdza natomiast coś, czego szczególnie trudno nauczyć automat:
czy człowiek myślący jak napastnik potrafi znaleźć drogę, której twórcy aplikacji nie przewidzieli.






