Skip to content
Powrót do bloga
Bezpieczeństwo

Bezpieczeństwo aplikacji webowych: hash, walidacja i uwierzytelnianie

Najlepsze praktyki bezpieczeństwa web: bcrypt vs Argon2, ochrona przed XSS i SQL injection, zalecenia dla JWT, nagłówki CSP i MFA — z przykładami w JS.

12 min czytania

Bezpieczeństwo aplikacji webowych: hash, walidacja i uwierzytelnianie

Bezpieczeństwo web nie jest opcjonalne. Wraz z rosnącą liczbą cyberzagrożeń programiści muszą wbudowywać zabezpieczenia w każdą warstwę swoich aplikacji. Niniejszy przewodnik omawia kluczowe praktyki, które warto wdrożyć już dziś.

Bezpieczeństwo haseł

Nigdy nie przechowuj haseł w postaci jawnej

Zawsze należy obliczać hash haseł przy użyciu nowoczesnych algorytmów, takich jak bcrypt, Argon2 czy scrypt. Algorytmy te zostały celowo zaprojektowane jako wolne, dzięki czemu ataki brute-force stają się nieopłacalne.

// Dobrze: użycie bcrypt
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);

Porównanie algorytmów hashujących

Nie wszystkie algorytmy hashujące są sobie równe. Wybór odpowiedniego zależy od modelu zagrożeń i przypadku użycia:

AlgorytmRozmiar wyjściaSzybkośćZastosowanieStatus bezpieczeństwa
MD5128 bitówBardzo szybkiSumy kontrolne, hashe niezwiązane z bezpieczeństwemKryptograficznie złamany
SHA-256256 bitówSzybkiIntegralność danych, podpisy cyfroweBezpieczny
bcrypt184 bityWolny (regulowany)Przechowywanie hasełBezpieczny
Argon2KonfigurowalnyWolny (regulowany)Przechowywanie haseł (nowoczesne)Zalecany dla nowych projektów

bcrypt i Argon2 są celowo wolne — to cecha, a nie wada. Każda operacja obliczania hashu trwa od dziesiątek do setek milisekund, co sprawia, że ataki brute-force na dużą skalę stają się ekonomicznie nieopłacalne.

Zrozumienie entropii hasła

Siłę hasła można mierzyć matematycznie za pomocą entropii: entropy = log2(charset_size^length). Hasło składające się z 8 małych liter (26 znaków) ma około 37,6 bita entropii. Hasło o długości 16 znaków, mieszające wielkie i małe litery, cyfry oraz symbole (95 znaków), osiąga około 105 bitów — wykładniczo trudniejsze do złamania. Dlatego dla większości użytkowników długość liczy się bardziej niż złożoność. Aby zgłębić matematykę stojącą za siłą hasła, warto zajrzeć do naszego przewodnika entropia hasła wyjaśniona.

Korzystaj z menedżera haseł

Warto zachęcać użytkowników do korzystania z menedżera haseł. Hasła wybierane przez ludzi mają tendencję do podążania przewidywalnymi wzorcami, które atakujący wykorzystują w atakach słownikowych. Menedżery haseł generują naprawdę losowe ciągi i eliminują powtarzanie haseł między usługami — jeden z najczęstszych wektorów ataków credential stuffing.

Stosuj wystarczającą liczbę rund soli

Liczba rund soli określa koszt obliczeniowy. Wyższa wartość oznacza większe bezpieczeństwo, ale wolniejsze działanie. 10–12 rund stanowi dobry kompromis dla większości aplikacji.

Walidacja danych wejściowych

Waliduj zarówno po stronie klienta, jak i serwera

Walidacja po stronie klienta poprawia UX, lecz walidacja po stronie serwera jest niezbędna dla bezpieczeństwa. Nigdy nie należy ufać danym pochodzącym od klienta.

Sanityzuj wszystkie dane wprowadzane przez użytkowników

Ataki typu injection można zablokować, sanityzując dane wejściowe:

  • Stosuj zapytania parametryzowane dla SQL
  • Escapuj wyjście HTML, aby zapobiec XSS
  • Rygorystycznie waliduj przesyłane pliki

Konkretne przykłady ataków

Zrozumienie rzeczywistych ataków pomaga się przed nimi bronić. Wyobraźmy sobie formularz komentarzy, który renderuje dane użytkownika bezpośrednio do HTML. Atakujący wysyła:

<script>alert('xss')</script>

Jeśli aplikacja wyrenderuje to bez escapowania, skrypt wykona się w przeglądarce każdego odwiedzającego — kradnąc ciasteczka, przekierowując użytkowników lub wstrzykując keyloggery. Rozwiązanie: zawsze koduj wyjście kontekstowo. Do sanityzacji HTML warto użyć bibliotek takich jak DOMPurify.

SQL injection jest równie groźny. W formularzu logowania atakujący wpisuje jako nazwę użytkownika:

' OR 1=1 --

Jeśli zapytanie zostanie zbudowane przez konkatenację stringów ("SELECT * FROM users WHERE username='" + input + "'"), całkowicie omija to uwierzytelnianie. Sekwencja -- zamienia resztę zapytania w komentarz. Rozwiązanie: zawsze stosuj zapytania parametryzowane (zwane też prepared statements). Wszystkie liczące się biblioteki bazodanowe je wspierają:

// ŹLE: konkatenacja stringów
db.query(`SELECT * FROM users WHERE username='${input}'`);

// DOBRZE: zapytanie parametryzowane
db.query('SELECT * FROM users WHERE username = $1', [input]);

Content Security Policy (CSP)

Jako warstwa obrony w głąb (defense-in-depth) warto wdrożyć nagłówki Content Security Policy. CSP informuje przeglądarkę, które źródła treści są zaufane, skutecznie blokując inline scripts i nieautoryzowane ładowanie zasobów. Nawet jeśli w kodzie istnieje podatność XSS, rygorystyczna polityka CSP może uniemożliwić wykonanie wstrzykniętego skryptu. Warto zacząć od Content-Security-Policy: default-src 'self' i stopniowo dodawać wyjątki w miarę potrzeb.

Funkcje hashujące

Wybór odpowiedniego hashu

Różne zastosowania wymagają różnych funkcji hashujących:

ZastosowanieZalecane
Hasłabcrypt, Argon2
IntegralnośćSHA-256
Sumy kontrolneSHA-256, MD5 (poza kontekstem bezpieczeństwa)
Szybki hashBLAKE3

Zrozumienie wyjścia hashu i kolizji

MD5 zwraca hash o długości 128 bitów (32 znaki szesnastkowe), natomiast SHA-256 generuje hash 256-bitowy (64 znaki szesnastkowe). Ta różnica ma znaczenie: większa przestrzeń wyjściowa oznacza wykładniczo więcej możliwych wartości hashu, co czyni kolizje znacznie mniej prawdopodobnymi. Kolizja zachodzi, gdy dwa różne wejścia generują ten sam hash — atakujący zdolny do wytworzenia kolizji może fałszować podpisy cyfrowe lub manipulować zweryfikowanymi danymi.

Kolizje MD5 można generować w ciągu sekund na współczesnym sprzęcie. SHA-256 pozostaje odporny na kolizje i nie są znane praktyczne ataki. Dlatego dobór właściwego algorytmu do właściwego kontekstu jest kluczowy:

  • Sumy kontrolne i deduplikacja: MD5 jest akceptowalny, gdy bezpieczeństwo nie ma znaczenia
  • Integralność danych i podpisy: SHA-256 zapewnia silną odporność na kolizje
  • Przechowywanie haseł: bcrypt lub Argon2, które stosują dodawanie soli i celową powolność

HMAC do uwierzytelniania wiadomości

Gdy konieczna jest weryfikacja zarówno integralności, jak i autentyczności wiadomości, należy użyć HMAC (Hash-based Message Authentication Code). HMAC łączy funkcję hashującą z tajnym kluczem, dzięki czemu tylko strony znające klucz mogą wygenerować lub zweryfikować tag. Jest to niezbędne do uwierzytelniania API, weryfikacji webhook oraz bezpiecznego generowania token.

Nigdy nie używaj MD5 ani SHA-1 do celów bezpieczeństwa

MD5 i SHA-1 są kryptograficznie złamane do zastosowań związanych z bezpieczeństwem. Do hashowania kryptograficznego należy używać SHA-256 lub SHA-3.

HTTPS wszędzie

Co naprawdę robi TLS

TLS (Transport Layer Security) zapewnia trzy kluczowe ochrony: szyfrowanie podczas przesyłania (uniemożliwia podsłuch), uwierzytelnianie serwera (potwierdza, że rozmawiamy z prawdziwym serwerem, a nie z podszywającym się) oraz integralność danych (wykrywa wszelkie modyfikacje w trakcie transmisji). Bez TLS wszystkie dane przesyłane między użytkownikami a serwerem — hasła, token, dane osobowe — wędrują w postaci jawnej.

Zawsze używaj TLS

  • Pozyskuj certyfikaty od zaufanych CA (Let’s Encrypt jest darmowy i w pełni zautomatyzowany)
  • Przekierowuj HTTP na HTTPS
  • Stosuj nagłówki HSTS
  • Aktualizuj wersje TLS

HSTS i mixed content

Nagłówki HTTP Strict Transport Security (HSTS) instruują przeglądarki, aby łączyły się wyłącznie przez HTTPS, nawet gdy użytkownik wpisze http://. Ustawienie Strict-Transport-Security: max-age=31536000; includeSubDomains wymusza tę zasadę przez cały rok dla wszystkich subdomen. Zapobiega to atakom SSL stripping, w których atakujący degraduje połączenie do HTTP.

Warto uważać na ostrzeżenia o mixed content: jeśli strona HTTPS ładuje obrazy, skrypty lub arkusze stylów przez HTTP, przeglądarka je zablokuje lub wyświetli ostrzeżenie. Należy przejrzeć strony pod kątem zakodowanych na sztywno adresów http:// i stosować ścieżki zależne od protokołu lub wymuszać HTTPS dla wszystkich zasobów.

Uwierzytelnianie

Wdróż rate limiting

Atakom brute-force można zapobiec dzięki rate limiting:

  • Ograniczaj liczbę prób logowania na adres IP
  • Dodawaj opóźnienia po nieudanych próbach
  • Stosuj CAPTCHA przy podejrzanej aktywności

Podstawy uwierzytelniania JWT

JSON Web Tokens (JWT) udostępniają bezstanowy mechanizm uwierzytelniania o strukturze header.payload.signature. Serwer podpisuje token tajnym kluczem, a klienci dołączają go do kolejnych żądań. Ponieważ token zawiera claim użytkownika, serwer nie musi przy każdym żądaniu sprawdzać stanu sesji — co sprawia, że JWT dobrze sprawdzają się w systemach rozproszonych i mikroserwisach.

Należy zawsze ustawiać krótkie czasy wygaśnięcia dla access token (np. 15 minut) i używać refresh token do uzyskiwania nowych access token. Refresh token trzeba przechowywać bezpiecznie (cookies httpOnly, nie localStorage) oraz wdrożyć rotację token, aby każdy refresh token mógł zostać użyty tylko raz.

Uwierzytelnianie wieloskładnikowe (MFA)

MFA nie jest już opcjonalne dla żadnej poważnej aplikacji. Wymaganie drugiego składnika — kodów TOTP (Google Authenticator), kluczy sprzętowych (YubiKey) lub powiadomień push — drastycznie zmniejsza skutki naruszenia haseł. Nawet jeśli atakujący zdobędzie poprawne dane uwierzytelniające, bez drugiego składnika nie zdoła się uwierzytelnić.

Zapobieganie session fixation

Ataki typu session fixation polegają na tym, że atakujący ustawia znany identyfikator sesji, zanim użytkownik się uwierzytelni. Po zalogowaniu atakujący wykorzystuje ten sam identyfikator sesji do przejęcia uwierzytelnionej sesji. Można temu zapobiec, zawsze regenerując identyfikator sesji po pomyślnym uwierzytelnieniu i unieważniając stary.

Stosuj bezpieczne zarządzanie sesjami

  • Generuj kryptograficznie losowe identyfikatory sesji
  • Ustawiaj flagi secure i httpOnly na ciasteczkach
  • Wdrażaj limity czasu sesji
  • Unieważniaj sesje przy wylogowaniu

Lista kontrolna nagłówków bezpieczeństwa

Wdrażanie odpowiednich nagłówków odpowiedzi HTTP to jeden z najskuteczniejszych i najmniej kosztownych sposobów na utwardzenie aplikacji. Poniżej skrócona tabela kluczowych nagłówków bezpieczeństwa:

NagłówekCelPrzykładowa wartość
Content-Security-PolicyZapobiega XSS i wstrzykiwaniu danychdefault-src 'self'
Strict-Transport-SecurityWymusza połączenia HTTPSmax-age=31536000; includeSubDomains
X-Content-Type-OptionsZapobiega MIME type sniffingnosniff
X-Frame-OptionsZapobiega clickjackingDENY
Referrer-PolicyKontroluje informacje o Refererstrict-origin-when-cross-origin

Nagłówki te można ustawić na poziomie serwera webowego (Nginx, Apache), na poziomie CDN/edge (Cloudflare, Vercel) lub w ramach framework aplikacyjnego. Warto przetestować nagłówki za pomocą narzędzi takich jak securityheaders.com. Cel to ocena A+ — większość tych nagłówków to pojedyncza linia konfiguracji i ich wdrożenie nic nie kosztuje.

Korzystanie z naszych narzędzi bezpieczeństwa

Warto zapoznać się z naszymi narzędziami bezpieczeństwa wspierającymi pracę programisty:

Aby uzyskać szerszy obraz tego, jak narzędzia do kodowania, hashowania i konwersji wpisują się w przepływ pracy programistycznej, warto sięgnąć po nasz przewodnik po niezbędnych narzędziach dla deweloperów.

Najczęściej zadawane pytania

Jaka jest najczęstsza podatność bezpieczeństwa w aplikacjach webowych?

Cross-Site Scripting (XSS) pozostaje najpowszechniejszą podatnością web według OWASP. Występuje, gdy aplikacje umieszczają niezaufane dane na stronach bez właściwej walidacji. XSS można zapobiec, sanityzując wszystkie dane wprowadzane przez użytkowników, stosując nagłówki Content Security Policy oraz kodując wyjście zależnie od kontekstu (HTML, JavaScript, URL lub CSS).

Czy MD5 jest nadal bezpieczny do hashowania haseł?

Nie — MD5 nigdy nie powinien być używany do hashowania haseł. Jest obliczeniowo szybki, przez co jest podatny na ataki brute-force i ataki z użyciem tablic tęczowych. Współczesne karty graficzne potrafią obliczać miliardy hashy MD5 na sekundę. Zamiast tego należy używać bcrypt, scrypt lub Argon2, które są celowo wolne i mają wbudowane dodawanie soli, aby przeciwdziałać atakom.

Jak długie powinno być bezpieczne hasło w 2026 roku?

Zaleca się minimum 12 znaków, ale 16+ znaków zapewnia znacznie silniejszą ochronę. Długość liczy się bardziej niż złożoność — 20-znakowy passphrase typu „correct-horse-battery-staple” jest silniejszy od krótkiego, złożonego hasła w rodzaju „P@ss1!”. Niezależnie od długości hasła, w przypadku kont krytycznych warto włączyć uwierzytelnianie wieloskładnikowe (MFA).

Jaka jest różnica między szyfrowaniem a hashowaniem?

Szyfrowanie jest odwracalne — dane można odszyfrować z powrotem do pierwotnej postaci za pomocą klucza. Hashowanie jest jednokierunkowe — z hashu nie da się odzyskać danych pierwotnych. Szyfrowania należy używać do danych, które trzeba odczytywać (jak przechowywane dane użytkowników), a hashowania — do danych, które wystarczy weryfikować (jak hasła i sumy kontrolne).

Czy warto budować własny system uwierzytelniania?

Nie — budowanie uwierzytelniania od zera jest ryzykowne i podatne na błędy. Lepiej korzystać ze sprawdzonych framework i usług, takich jak Auth0, Firebase Auth czy Supabase Auth. Obsługują one przechowywanie haseł, zarządzanie sesjami, rotację token, MFA oraz ochronę przed brute-force. Czas programistów warto przeznaczyć na unikalne funkcje aplikacji.

Podsumowanie

Bezpieczeństwo to proces ciągły, a nie jednorazowe zadanie. Należy śledzić nowe podatności, regularnie audytować kod i przestrzegać zasady najmniejszych uprawnień. Użytkownicy powierzają nam swoje dane — warto uszanować to zaufanie solidnymi praktykami bezpieczeństwa.

Powiązane artykuły

Zobacz wszystkie artykuły