1. Aplikacje i Usługi Sieciowe

1.3. Bezpieczeństwo w aplikacjach sieciowych

Bezpieczeństwo w aplikacjach sieciowych jest jednym z najważniejszych aspektów współczesnych systemów informatycznych. Wraz z rozwojem Internetu, rosnącą liczbą użytkowników, złożonością systemów i integracją rozproszonych usług, zagrożenia związane z bezpieczeństwem stają się coraz bardziej istotne. Aplikacje sieciowe – czyli takie, które komunikują się za pośrednictwem sieci, niezależnie od tego, czy są to systemy bankowe, portale społecznościowe, sklepy internetowe, interfejsy API czy aplikacje mobilne – narażone są na cały wachlarz ataków, manipulacji i nadużyć. Bezpieczeństwo tych aplikacji nie jest już jedynie technicznym aspektem implementacji, ale częścią całego procesu projektowania, testowania, wdrażania i utrzymania oprogramowania.

Podstawowym celem bezpieczeństwa aplikacji sieciowych jest ochrona poufności, integralności i dostępności danych. Poufność oznacza, że informacje są dostępne tylko dla uprawnionych użytkowników. Integralność – że dane nie zostały nieautoryzowanie zmodyfikowane. Dostępność – że system pozostaje aktywny i gotowy do działania w każdej chwili. Aby osiągnąć te cele, aplikacje muszą wdrażać różnorodne mechanizmy zabezpieczeń – od warstwy transportowej i uwierzytelniania, przez kontrolę dostępu, po zabezpieczenia przed konkretnymi atakami sieciowymi i aplikacyjnymi. Współczesna aplikacja sieciowa nie jest systemem zamkniętym – komunikuje się z innymi usługami, korzysta z zewnętrznych API, przechowuje dane w chmurze, a interfejs użytkownika działa w środowisku niekontrolowanym (np. w przeglądarce). To oznacza, że zagrożenia mogą pojawić się w każdym miejscu łańcucha komunikacyjnego.

Jednym z podstawowych aspektów bezpieczeństwa aplikacji sieciowych jest stosowanie szyfrowania transmisji danych. Każda aplikacja, która przesyła dane przez Internet, powinna wykorzystywać HTTPS zamiast HTTP. HTTPS to HTTP nad protokołem TLS (Transport Layer Security), który zapewnia szyfrowanie danych pomiędzy klientem a serwerem. Dzięki temu żaden podmiot pośredniczący – router, punkt dostępowy, proxy – nie jest w stanie odczytać zawartości pakietów. TLS zabezpiecza również integralność transmisji (czyli wykrywa modyfikacje danych) oraz uwierzytelnia serwer (certyfikaty SSL/TLS wystawione przez zaufane urzędy certyfikacji).

Rysunek: kanał szyfrowany TLS

Poza szyfrowaniem transmisji, aplikacja musi zapewnić silne mechanizmy uwierzytelniania użytkowników i usług. Uwierzytelnianie to proces weryfikacji tożsamości. Może być realizowane w postaci loginu i hasła, certyfikatu klienta, tokenu sesji, kodu SMS lub klucza sprzętowego. W aplikacjach webowych powszechnie stosuje się sesje z identyfikatorem zapisywanym w ciasteczku HTTP-Only oraz tokeny typu JWT (JSON Web Token), które przechowywane są w pamięci lokalnej lub przesyłane w nagłówkach. JWT zawiera zakodowane informacje o użytkowniku i jego uprawnieniach oraz podpis cyfrowy serwera, który gwarantuje autentyczność tokena.

Autoryzacja to odrębny proces – oznacza przyznanie lub odmowę dostępu do konkretnego zasobu po potwierdzeniu tożsamości. Może być realizowana przez sprawdzenie ról użytkownika, przynależności do grupy, poziomu dostępu, flagi właściciela lub mechanizmów typu ACL (Access Control List). Dobre praktyki wymagają rozdzielenia uwierzytelniania od autoryzacji, tak by jedna warstwa nie polegała wyłącznie na drugiej. Szczególnie ważne jest unikanie sytuacji, w których np. użytkownik po prostu odwołuje się do zasobu przez adres URL, który nie został do niego przypisany.

Wiele ataków na aplikacje sieciowe wynika z błędów w implementacji mechanizmów uwierzytelniania i autoryzacji. Przykładowo: brak sprawdzania tożsamości w zapytaniach typu GET/POST, przewidywalne identyfikatory sesji, brak ochrony przed powtórnym wykorzystaniem tokena, brak ograniczeń prób logowania (brute force). Istotne jest również stosowanie ochrony przed atakami typu CSRF (Cross-Site Request Forgery), które polegają na wymuszeniu przez złośliwy serwis wykonania działania na autoryzowanym koncie użytkownika bez jego wiedzy.

Rysunek: schemat ataku CSRF

Innym kluczowym zagadnieniem bezpieczeństwa aplikacji sieciowych jest ochrona przed atakami typu Injection – wstrzykiwanie kodu. Najpopularniejszy przykład to SQL Injection, w którym atakujący wprowadza dane wejściowe zawierające fragmenty poleceń SQL, które nie są poprawnie oczyszczane i są bezpośrednio wstawiane do zapytania. Skutkiem może być uzyskanie dostępu do bazy danych, odczyt danych innych użytkowników, modyfikacja zawartości, a w skrajnych przypadkach nawet eskalacja uprawnień.

Tego typu ataki są możliwe, gdy dane użytkownika są łączone ze stałymi ciągami znaków bez sanitizacji. Ochroną przed injection jest stosowanie tzw. zapytań parametryzowanych (prepared statements), które oddzielają logikę zapytania od danych wejściowych. W nowoczesnych bibliotekach ORM (Object-Relational Mapping) takie zabezpieczenia są domyślnie wbudowane, ale mogą być obchodzone przy niestandardowych manipulacjach ciągami zapytań.

Aplikacje sieciowe muszą być również odporne na ataki typu XSS (Cross-Site Scripting), które polegają na wstrzyknięciu złośliwego kodu JavaScript do aplikacji, który następnie jest uruchamiany w przeglądarce innych użytkowników. Kod ten może np. przechwytywać ciasteczka, modyfikować zawartość strony lub przekierowywać na złośliwe adresy. XSS dzieli się na trzy główne typy: reflected (kod zwracany natychmiast), stored (kod zapisany w bazie danych i serwowany innym użytkownikom) oraz DOM-based (manipulacje wewnątrz kodu JavaScript w przeglądarce).

Ochrona przed XSS polega na kodowaniu wszystkich danych wprowadzanych przez użytkownika przy wyświetlaniu ich na stronie. W przypadku HTML – zamiana znaków < i > na < i >, w przypadku JavaScript – stosowanie odpowiednich konstrukcji literalnych, w przypadku URL – kodowanie procentowe. Dodatkowo stosuje się nagłówki bezpieczeństwa (np. Content-Security-Policy), które ograniczają możliwość uruchamiania nieautoryzowanego kodu.

Zabezpieczenia aplikacji muszą też obejmować ochronę przed utratą dostępności, szczególnie w kontekście ataków DDoS (Distributed Denial of Service), które polegają na wysyłaniu olbrzymiej liczby żądań w krótkim czasie, co prowadzi do zablokowania lub spowolnienia usługi. Ochrona obejmuje filtrowanie ruchu, użycie WAF (Web Application Firewall), mechanizmy rate limiting, captche, a także usługi chmurowe do filtrowania ruchu na poziomie warstwy 3 i 4.

Kolejnym zagadnieniem bezpieczeństwa jest przechowywanie danych – szczególnie haseł i informacji osobowych. Hasła nigdy nie powinny być przechowywane w postaci jawnej, nawet zaszyfrowanej. Standardem jest stosowanie silnych, jednokierunkowych funkcji skrótu (np. bcrypt, Argon2), które utrudniają odtworzenie hasła z jego odcisku. Bazy danych powinny być również szyfrowane, dostęp do nich powinien być ograniczony przez kontrolę dostępu, a system powinien rejestrować próby nieautoryzowanego dostępu.

Współczesne aplikacje sieciowe często korzystają z autoryzacji opartej na tokenach OAuth2 i OpenID Connect, które umożliwiają bezpieczne logowanie przez zewnętrzne systemy (np. Google, Facebook, Azure AD). Protokół OAuth2 umożliwia przekazywanie uprawnień między systemami bez udostępniania haseł, przez użycie tymczasowych tokenów dostępu i tokenów odświeżania. Stosowanie tych rozwiązań wymaga jednak dokładnej implementacji, w tym ochrony tokenów przed kradzieżą (TLS, httpOnly cookies), ograniczania ich zakresu i czasu życia.


Rysunek: schemat OAuth2

Ważnym elementem bezpieczeństwa aplikacji sieciowych jest regularne testowanie – zarówno manualne, jak i automatyczne. Stosuje się testy penetracyjne, audyty kodu, skanery podatności (np. OWASP ZAP, Burp Suite), systemy SAST/DAST, oraz analizę logów. Standardy takie jak OWASP Top 10 definiują najczęstsze i najbardziej niebezpieczne podatności występujące w aplikacjach webowych. Należy do nich m.in. nieautoryzowany dostęp, nieprawidłowa konfiguracja zabezpieczeń, nadużycie zasobów, brak walidacji wejścia, podatności na komponenty open-source. Zespół projektowy powinien znać te klasy i umieć im przeciwdziałać.

Ostatnią warstwą bezpieczeństwa jest użytkownik i jego interakcja z aplikacją. Projektując interfejsy należy unikać niejasnych komunikatów, stosować jednoznaczne ostrzeżenia, zabezpieczać formularze, zapewniać informację o czasie trwania sesji i umożliwiać jej zakończenie. Aplikacja powinna też informować o nieudanych próbach logowania, wymuszać silne hasła, oferować dwuskładnikowe uwierzytelnianie i umożliwiać sprawdzenie historii logowań.