Przejdź do treści
Cyberbezpieczeństwo

CSRF (Cross-Site Request Forgery) – jak działa atak i jak się przed nim chronić

CSRF (Cross-Site Request Forgery) wykorzystuje ciasteczka sesji do wykonania akcji w Twoim imieniu. Zobacz przykład ataku, rolę SameSite i skuteczne zabezpieczenia.

CZCzarek ZawolskiAktualizacja: 7 min czytania
Przeglądarka wysyłająca sfałszowane żądanie do aplikacji internetowej z ciasteczkiem sesji

W skrócie

  • CSRF zmusza przeglądarkę zalogowanego użytkownika do wysłania żądania do innej aplikacji – przeglądarka sama dołącza ciasteczka sesji, więc serwer uznaje je za prawdziwe.
  • Podstawową obroną jest token CSRF (anti-forgery token) w każdym żądaniu zmieniającym stan, sprawdzany po stronie serwera.
  • Atrybut SameSite=Lax lub Strict w ciasteczkach sesji oraz nagłówki Fetch Metadata (Sec-Fetch-Site) to mocna druga warstwa ochrony.
  • Żądania GET nigdy nie powinny zmieniać danych – SameSite=Lax ich nie blokuje.
  • Podatność XSS na tej samej stronie unieważnia ochronę przed CSRF, bo skrypt atakującego może odczytać token.
Spis treści

CSRF (Cross-Site Request Forgery, fałszowanie żądań między witrynami) to atak, w którym obca strona zmusza przeglądarkę zalogowanego użytkownika do wysłania żądania do innej aplikacji – na przykład zmiany adresu e-mail, hasła czy wykonania przelewu. Działa, bo przeglądarka automatycznie dołącza do żądania ciasteczka sesji, a serwer nie potrafi odróżnić takiego żądania od kliknięcia samego użytkownika.

Skuteczna obrona to przede wszystkim tokeny CSRF w żądaniach zmieniających dane, uzupełnione o atrybut SameSite w ciasteczkach i weryfikację pochodzenia żądania. Poniżej pokazuję na przykładzie, jak przebiega atak, dlaczego ciasteczka są tu kluczowe i jak poprawnie zabezpieczyć aplikację.

Jak działa atak CSRF – krok po kroku

Weźmy prosty sklep internetowy, w którym zmiana adresu e-mail konta odbywa się formularzem wysyłanym metodą POST na /konto/email, a użytkownik jest rozpoznawany po ciasteczku sesji.

  1. Ofiara loguje się do sklepu. Przeglądarka zapisuje ciasteczko session=....
  2. W innej karcie ofiara otwiera stronę atakującego – z linku w mailu, reklamy, komentarza na forum.
  3. Strona atakującego zawiera ukryty formularz, który wysyła się automatycznie:
<form action="https://sklep.example.pl/konto/email" method="POST" id="f">
  <input type="hidden" name="email" value="atakujacy@example.com">
</form>
<script>document.getElementById('f').submit();</script>
  1. Przeglądarka wysyła żądanie POST do sklepu i – zgodnie ze standardowym zachowaniem – dołącza ciasteczko sesji ofiary.
  2. Serwer widzi poprawną sesję, zmienia adres e-mail i potwierdza operację.
  3. Atakujący używa funkcji „nie pamiętam hasła”, a link resetujący trafia na jego adres. Konto jest przejęte.

Atakujący nie widzi odpowiedzi serwera ani ciasteczek ofiary – nie musi. Wystarczy mu, że akcja się wykona. Dlatego CSRF dotyczy operacji zmieniających stan: zmian danych konta, ustawień, zamówień, przelewów, uprawnień.

Dlaczego to ciasteczka są problemem

Ciasteczka zostały zaprojektowane tak, by przeglądarka wysyłała je do domeny przy każdym żądaniu, niezależnie od tego, która strona to żądanie zainicjowała. Ta wygoda (nie trzeba się ciągle logować) jest jednocześnie źródłem podatności. Każdy mechanizm uwierzytelniania wysyłany automatycznie – ciasteczka, uwierzytelnianie HTTP Basic, certyfikaty klienta, a w sieci firmowej także zintegrowane uwierzytelnianie Windows – może być wykorzystany w ataku CSRF.

Inaczej jest z tokenami wysyłanymi ręcznie przez JavaScript w nagłówku Authorization: Bearer .... Przeglądarka nie doda ich sama do żądania z obcej strony, więc takie API jest w dużej mierze odporne na CSRF. Ceną jest konieczność przechowywania tokenu w pamięci lub w Local Storage, co z kolei zwiększa skutki ewentualnego XSS.

Typowe scenariusze i przykłady CSRF

  • Zmiana e-maila lub hasła bez potwierdzenia starym hasłem – prowadzi do przejęcia konta.
  • Akcje w panelu administracyjnym – administrator CMS odwiedza spreparowaną stronę, a w tle tworzy się nowe konto administratora albo instaluje wtyczka.
  • Routery i urządzenia w sieci domowej – panel routera pod 192.168.1.1 z domyślnym hasłem lub aktywną sesją pozwala zmienić serwery DNS i przekierować cały ruch domowy.
  • Login CSRF – atakujący loguje ofiarę na swoje konto, a potem obserwuje, co ofiara na nim robi (np. jakie dane karty wpisze).
  • Żądania GET zmieniające stan – link typu /usun?id=15 można osadzić nawet w tagu <img> na forum; wystarczy wyświetlić stronę.

CSRF przez lata był w czołówce zestawienia OWASP Top 10. W edycji z 2017 r. zniknął z listy jako osobna pozycja, bo popularne frameworki zaczęły chronić przed nim domyślnie, a od 2021 r. słabość ta (CWE-352) jest klasyfikowana w kategorii Broken Access Control. Nie znaczy to, że zniknęła – wciąż pojawia się w aplikacjach pisanych bez frameworka, we wtyczkach CMS i panelach urządzeń.

Jak chronić aplikację przed CSRF

Rekomendacje OWASP (CSRF Prevention Cheat Sheet) sprowadzają się do kilku warstw. Najważniejsza jest pierwsza.

1. Tokeny CSRF (synchronizer token pattern)

Serwer generuje losowy, nieprzewidywalny token powiązany z sesją użytkownika, umieszcza go w formularzu (lub udostępnia skryptom) i przy każdym żądaniu zmieniającym stan sprawdza, czy token się zgadza. Strona atakującego nie zna tokenu, bo nie może odczytać treści Twojej strony (blokuje to polityka Same-Origin).

Minimalna implementacja w czystym PHP:

<?php
session_start();

// Generowanie tokenu raz na sesję
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Weryfikacja przy żądaniu POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_token'], $token)) {
        http_response_code(403);
        exit('Nieprawidłowy token CSRF');
    }
}
?>
<form method="POST" action="/konto/email">
  <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
  <input type="email" name="email">
  <button>Zapisz</button>
</form>

Do porównania używaj hash_equals(), a nie ==, żeby uniknąć ataków czasowych. W aplikacjach SPA token przekazuje się zwykle w nagłówku, np. X-CSRF-Token.

2. Wbudowana ochrona frameworka

Nie pisz własnej ochrony, jeśli framework ją ma – i nie wyłączaj jej „bo przeszkadza”:

FrameworkMechanizm
DjangoCsrfViewMiddleware i tag {% csrf_token %} w formularzach
LaravelMiddleware weryfikujące token i dyrektywa @csrf w Blade
SymfonyKomponent CSRF, automatycznie w formularzach
Spring SecurityOchrona CSRF włączona domyślnie
ASP.NET CoreAntiforgery – automatycznie w formularzach Razor, atrybut [ValidateAntiForgeryToken]
WordPressNonce: wp_nonce_field(), check_admin_referer(), wp_verify_nonce()

W Node.js/Express popularna kiedyś paczka csurf jest wycofana – wybierz aktywnie utrzymywaną alternatywę lub mechanizm wbudowany w używany framework.

3. Atrybut SameSite w ciasteczkach

Atrybut SameSite mówi przeglądarce, kiedy wysyłać ciasteczko w żądaniach inicjowanych przez inne witryny:

  • SameSite=Strict – ciasteczko nigdy nie jest wysyłane z obcej witryny, nawet po kliknięciu zwykłego linku (użytkownik wchodzący z linku w mailu będzie „wylogowany” przy pierwszym wejściu),
  • SameSite=Lax – ciasteczko jest wysyłane tylko przy nawigacji najwyższego poziomu metodą GET (kliknięcie linku), ale nie w ukrytych formularzach POST, ramkach ani żądaniach fetch,
  • SameSite=None – zawsze, wymaga atrybutu Secure.

Przykładowe bezpieczne ustawienie ciasteczka sesji:

Set-Cookie: session=7f3c...; Path=/; Secure; HttpOnly; SameSite=Lax

Chrome od 2020 r. traktuje ciasteczka bez atrybutu SameSite jako Lax, ale nie wszystkie przeglądarki robią to samo – ustawiaj go zawsze jawnie.

Uwaga: SameSite chroni przed żądaniami z innych witryn (site), a nie innych źródeł (origin). Subdomeny tej samej domeny, np. blog.firma.pl i panel.firma.pl, są dla przeglądarki tą samą witryną. Przejęta lub podatna subdomena może więc przeprowadzić CSRF mimo SameSite. Dlatego SameSite to dodatkowa warstwa, a nie zamiennik tokenów.

4. Weryfikacja pochodzenia: Origin i Fetch Metadata

Nowoczesne przeglądarki wysyłają nagłówki, które pozwalają serwerowi sprawdzić, skąd przyszło żądanie:

  • Origin – źródło strony, z której wysłano żądanie (np. https://zla-strona.example),
  • Sec-Fetch-Site – wartość same-origin, same-site, cross-site lub none (gdy użytkownik wpisał adres sam).

Prosta reguła po stronie serwera może odrzucać wszystkie żądania zmieniające stan, które przyszły z obcych witryn:

$site = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? null;
$method = $_SERVER['REQUEST_METHOD'];

if ($method !== 'GET' && $method !== 'HEAD' && $site !== null
    && !in_array($site, ['same-origin', 'none'], true)) {
    http_response_code(403);
    exit;
}

Sprawdzanie nagłówka Referer jest mniej niezawodne – bywa usuwany przez ustawienia prywatności i proxy – ale lepsze to niż nic, jeśli nie masz innych mechanizmów.

5. Dobre praktyki projektowe

  • Żądania GET i HEAD nigdy nie zmieniają danych. To zasada HTTP, a jednocześnie warunek skuteczności SameSite=Lax.
  • Krytyczne operacje (zmiana e-maila, hasła, danych do wypłat, dodanie administratora) wymagają ponownego podania hasła lub potwierdzenia drugim składnikiem.
  • Nie polegaj na CORS jako ochronie przed CSRF. Zwykły formularz HTML (typy application/x-www-form-urlencoded, multipart/form-data, text/plain) jest wysyłany bez zapytania preflight, a serwer wykonuje akcję, zanim przeglądarka zablokuje odczyt odpowiedzi.
  • Wyeliminuj XSS. Skrypt wstrzyknięty na Twoją stronę może odczytać token CSRF i wysłać poprawne żądanie – więcej w artykule o Cross Site Scripting.

CSRF a inne ataki na sesję

CSRF często myli się z innymi atakami związanymi z ciasteczkami:

AtakCo robi atakującyCzy zna ciasteczko ofiary?
CSRFPodsuwa przeglądarce ofiary sfałszowane żądanieNie
XSSUruchamia własny skrypt na podatnej stronieMoże je odczytać, jeśli nie ma HttpOnly
Przejęcie sesjiKradnie identyfikator sesji i używa go u siebieTak
ClickjackingNakłada niewidoczną ramkę i nakłania do kliknięciaNie

Przejmowanie sesji opisujemy w tekście o ataku Session Hijacking. Przed clickjackingiem chroni nagłówek Content-Security-Policy: frame-ancestors 'self' (lub starszy X-Frame-Options: DENY).

Jak sprawdzić, czy aplikacja jest podatna

  1. Zrób listę wszystkich akcji zmieniających stan: formularzy, endpointów API, akcji w panelu administracyjnym.
  2. Dla każdej sprawdź, czy żądanie zawiera token CSRF i czy serwer odrzuca żądanie bez tokenu lub z błędnym tokenem.
  3. Sprawdź atrybuty ciasteczek sesji w narzędziach deweloperskich przeglądarki (karta Application/Storage → Cookies).
  4. Przygotuj prostą stronę testową z formularzem jak w przykładzie powyżej, otwórz ją w przeglądarce z aktywną sesją i zobacz, czy akcja się wykona.
  5. Użyj skanera, np. Burp Suite lub OWASP ZAP – oba mają funkcje generowania dowodu koncepcji CSRF.

Przy większych aplikacjach takie sprawdzenie warto zlecić w ramach testów bezpieczeństwa. Jako użytkownik najwięcej zrobisz, wylogowując się z bankowości i paneli administracyjnych po zakończeniu pracy i nie otwierając podejrzanych linków w tej samej przeglądarce, w której masz aktywne ważne sesje.

Najczęściej zadawane pytania

Co to jest atak CSRF?

Cross-Site Request Forgery to atak, w którym złośliwa strona powoduje, że przeglądarka ofiary wysyła żądanie do aplikacji, w której ofiara jest zalogowana. Przeglądarka automatycznie dołącza ciasteczka sesji, więc aplikacja wykonuje akcję tak, jakby zlecił ją użytkownik.

Jak zabezpieczyć się przed CSRF?

Stosuj tokeny CSRF w formularzach i żądaniach zmieniających stan, ustawiaj ciasteczkom sesji atrybut SameSite=Lax lub Strict, weryfikuj nagłówki Origin lub Sec-Fetch-Site i nie wykonuj zmian danych w żądaniach GET. Najlepiej korzystać z wbudowanej ochrony frameworka.

Czym różni się CSRF od XSS?

XSS polega na wstrzyknięciu i wykonaniu skryptu atakującego w kontekście podatnej strony. CSRF nie uruchamia kodu na atakowanej stronie, tylko podsuwa przeglądarce sfałszowane żądanie. XSS jest groźniejszy, bo pozwala obejść zabezpieczenia przed CSRF.

Czy SameSite wystarczy do ochrony przed CSRF?

Nie zawsze. SameSite=Lax nie chroni żądań GET wywołanych nawigacją, nie działa między subdomenami tej samej domeny, a starsze przeglądarki mogą go ignorować. Traktuj go jako dodatkową warstwę, a nie jedyne zabezpieczenie.

Czy API z tokenem w nagłówku Authorization jest podatne na CSRF?

Zasadniczo nie, bo przeglądarka nie dołącza takiego tokenu automatycznie do żądań z innych stron. Problem wraca, gdy token przechowywany jest w ciasteczku – wtedy API wymaga takiej samej ochrony jak zwykła aplikacja.

CZ

Autor

Czarek Zawolski

Założyciel i redaktor XAD.pl. Pisze o sieciach, bezpieczeństwie IT, administracji systemami Windows i Linux oraz o sprzęcie, który sprawia ludziom problemy na co dzień.