# Ochrana pred CSRF v aplikáciách PHP

> Ochrana pred CSRF v PHP: synchronizer token, kontrola Origin a Sec-Fetch-Site, cookies SameSite a login CSRF. S ukážkou kódu a kontrolným zoznamom.

Zdroj: https://phpsos.de/sk/blog/csrf-schutz-php/  
Stav: 25.10.2026  
Poskytovateľ: PHP SOS, Núdzová pomoc pri PHP a bezpečnosť serverov. Okamžitá pomoc cez WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20de%C5%88%2C%20PHP%20SOS%2C%20potrebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20serverom%20s%20PHP.%20Spolo%C4%8Dnos%C5%A5%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručne:** CSRF v PHP zabránite tak, že každá požiadavka, ktorá niečo mení, musí priniesť tajný token viazaný na reláciu, ktorý na serveri overíte pomocou `hash_equals()`. Dodatočne kontrolujete hlavičky `Origin` alebo `Sec-Fetch-Site` a cookies relácie nastavujete s `SameSite`. Požiadavky GET nikdy nesmú meniť údaje a ochranu potrebuje aj prihlasovací formulár.

## Kliknutie, ktoré nikto nezbadá

Zamestnankyňa je prihlásená v backende obchodu a v druhej karte otvorí cudziu stránku. Táto stránka na pozadí odošle formulár do backendu, napríklad na zmenu e-mailovej adresy alebo založenie účtu. Prehliadač automaticky pripojí cookie relácie. Pre aplikáciu vyzerá požiadavka ako každá iná z vlastného tímu.

To je Cross-Site Request Forgery, skrátene CSRF: požiadavka, ktorú prihlásený používateľ nevedomky pošle aplikácii, ktorej dôveruje. Útočník odpoveď nevidí, ale ju ani nepotrebuje. Stačí, že sa akcia vykoná.

CSRF sa nedostáva do titulkov ako PHP2Shell, ale vo vlastnoručne vyvinutých aplikáciách PHP je to častý nález. Frameworky ako Symfony a Laravel prinášajú ochranu so sebou; medzery vznikajú tam, kde bola pre jednotlivé trasy vypnutá. Vlastné vývoje bez frameworku často nemajú žiadnu.

## CSRF v PHP: ktoré požiadavky treba chrániť

Ochranu potrebujú všetky požiadavky, ktoré menia stav: ukladanie, mazanie, objednávanie, prideľovanie práv, zmena nastavení, nahrávanie súborov, odhlásenie. Čítacie volania token nepotrebujú, pokiaľ naozaj iba čítajú.

Z toho vyplýva prvé pravidlo: **žiadna zmena cez GET.** Odkazy ako `loeschen.php?id=…` sa dajú z cudzích stránok vyvolať obzvlášť ľahko, napríklad cez vložený obrázok. Akcie, ktoré menia, patria do POST, PUT, PATCH alebo DELETE.

Druhé pravidlo: **chrániť aj prihlásenie.** Pri takzvanom login CSRF je obeť nepozorovane prihlásená do účtu útočníka. Ak tam zadá platobné údaje alebo dokumenty, skončia u útočníka. Token v prihlasovacom formulári a nové ID relácie po úspešnom prihlásení tomu zabránia.

## Vzor Synchronizer Token v PHP

Pre klasické aplikácie s reláciami odporúča OWASP vzor Synchronizer Token: server vytvorí náhodný token, uloží ho do relácie a očakáva ho späť v každom formulári, ktorý niečo mení. Dôležité je, aby sa token posielal vo formulári alebo vo vlastnej hlavičke, nie v cookie.

```php
<?php
declare(strict_types=1);

function csrfToken(): string
{
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function csrfPruefen(): void
{
    $gesendet = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!is_string($gesendet) || !hash_equals(csrfToken(), $gesendet)) {
        http_response_code(403);
        exit('Anfrage abgelehnt.');
    }
}

if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    csrfPruefen();
}
?>
<form method="post" action="/konto/email">
    <input type="hidden" name="csrf_token"
           value="<?= htmlspecialchars(csrfToken(), ENT_QUOTES, 'UTF-8') ?>">
    <!-- ďalšie polia -->
</form>
```

`hash_equals()` porovnáva v konštantnom čase, takže čas odpovede neprezradí nič o správnej hodnote. Po prihlásení by ste mali ID relácie obnoviť pomocou `session_regenerate_id(true)` a vytvoriť nový token. Prečo je `random_bytes()` správnym zdrojom a ktoré funkcie sú nevhodné, vysvetľuje [Bezpečné náhodné čísla a tokeny v PHP](https://phpsos.de/sk/blog/sichere-tokens-php/).

Jeden token na reláciu pre väčšinu aplikácií stačí. Tokeny na formulár alebo na požiadavku ochranu trochu zvyšujú, vedú však k problémom s viacerými otvorenými kartami a tlačidlom Späť. Dôležitejšie než frekvencia zmeny je, aby sa token nikdy neobjavil v URL, kde môže skončiť v protokoloch, v histórii prehliadača a v údajoch referrer pre cudzie stránky.

Pre bezstavové aplikácie bez relácie uvádza OWASP ako alternatívu podpísané cookie Double Submit: token viazaný na reláciu pomocou HMAC. Jednoduchý, nepodpísaný variant sa výslovne neodporúča.

## Druhá vrstva: Origin, Sec-Fetch-Site a SameSite

Moderné prehliadače posielajú pri požiadavkách informácie o svojom pôvode. Hlavička `Origin` uvádza volajúcu stránku, `Sec-Fetch-Site` hovorí, či požiadavka pochádza z tej istej stránky (`same-origin`), príbuznej (`same-site`) alebo cudzej (`cross-site`). Z toho sa dá zostaviť jednoduchá dodatočná kontrola:

```php
<?php
$erlaubteHerkunft = 'https://www.example.de';
$fetchSite = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? null;
$origin    = $_SERVER['HTTP_ORIGIN'] ?? null;

$verdaechtig = ($fetchSite !== null && $fetchSite !== 'same-origin')
    || ($fetchSite === null && $origin !== null && $origin !== $erlaubteHerkunft);

if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $verdaechtig) {
    http_response_code(403);
    exit;
}
```

OWASP pritom zdôrazňuje dva body: pôvod sa musí porovnávať úplne, aby neprešli podobne znejúce domény, a chýbajúca hlavička alebo hodnota `null` sa nesmie považovať za vlastný pôvod. Keďže staršie prehliadače hlavičky neposielajú vždy, hlavnou obranou zostáva token.

Treťou vrstvou sú cookies relácie s `SameSite`. PHP podporuje `session.cookie_samesite` od verzie 7.3, štandardná hodnota je prázdna. Nastavte aspoň `Lax`, pre čisté backendy je často možné `Strict`. Ostatné nastavenia cookies opisuje sprievodca [Zabezpečenie relácií v PHP](https://phpsos.de/sk/ratgeber/session-sicherheit-php/).

Ak používate CMS, zvyčajne prináša vlastné nástroje. WordPress napríklad pracuje s takzvanými nonce, ktoré pluginy zapájajú cez funkcie ako `wp_nonce_field()` a `check_admin_referer()`. Medzery v praxi vznikajú najmä v rozšíreniach, ktoré takéto kontroly pri jednotlivých akciách zabudnú. Preto skontrolujte najmä vlastnoručne napísané alebo málo rozšírené pluginy.

Pri rozhraniach, ktoré prehliadače používajú z iných domén, rozhoduje navyše konfigurácia CORS. Ak server vráti ako povolený každý pôvod požiadavky a zároveň povolí cookies, sám vyradí ochranu prehliadača. OWASP odporúča otvoriť CORS len pre výslovne dôveryhodné pôvody; plošné povolenie všetkých subdomén sa stane problémom, hneď ako sa niektorá z nich dostane do cudzích rúk.

## Kontrolný zoznam pre existujúce aplikácie

Pri prerastených projektoch pomáha systematické prehliadnutie:

1. Zachytiť všetky trasy a skripty, ktoré menia údaje, aj koncové body Ajax a nahrávanie súborov.
2. Skontrolovať, či sa na to používa GET, a prestaviť na POST alebo vhodné metódy.
3. Pre každú trasu, ktorá niečo mení, preukázať, že sa token overuje; výnimky vo frameworku hľadať cielene.
4. Prihlásenie, odhlásenie a zmenu hesla otestovať osobitne.
5. Doplniť `session.cookie_samesite` a kontrolu pôvodu.
6. Odstrániť zraniteľnosti XSS, pretože votrelecký kód môže čítať každý token. Postup opisuje [Ako zabrániť Cross-Site Scripting v PHP](https://phpsos.de/sk/ratgeber/xss-php/).

> **Dôležité:**
> Ochrana pred CSRF, ktorá sa kontroluje len vo frontende cez JavaScript, je neúčinná. Kontrola sa musí uskutočniť na serveri skôr, než sa akcia vykoná.

## Ako konkrétne pomáha PHP SOS?

- Zachytíme všetky trasy vašej aplikácie PHP, ktoré niečo menia, a skontrolujeme, kde token chýba alebo bol vypnutý.
- Zabudujeme centrálny kontrolný bod namiesto rozptýlených kontrol tokenov po mnohých skriptoch.
- Doplníme kontrolu pôvodu a nastavenia SameSite bez rušenia platobných alebo prihlasovacích postupov.
- Po prestavbe znova otestujeme a výsledky zdokumentujeme.

Úplnú kontrolu vašej aplikácie opisuje služba [Bezpečnostný audit a penetračný test](https://phpsos.de/sk/leistungen/sicherheitsaudit-penetrationstest/).


## Časté otázky

**Stačí SameSite=Lax ako ochrana pred CSRF?**  
Nie. Cookies SameSite sú podľa OWASP dodatočná vrstva, nie náhrada. Lax naďalej posiela cookies pri bežných zobrazeniach stránok cez GET a útoky zo subdomén toho istého webu nepokrýva.

**Potrebujú API JSON token CSRF?**  
Ak sa API overuje pomocou cookies, áno, alebo rovnocennú kontrolu. Alternatívou sú vlastné hlavičky požiadavky, ktoré prehliadače pri cudzích stránkach povolia až po predbežnej kontrole CORS. Povolenie CORS potom smie obsahovať len výslovne dôveryhodné pôvody.

**Musí byť chránený aj prihlasovací formulár?**  
Áno. Pri login CSRF je obeť nepozorovane prihlásená do cudzieho účtu a zadáva tam vlastné údaje. Token v prihlasovacom formulári a nová relácia po prihlásení tomu zabránia.

**Chráni ochrana pred CSRF aj pred XSS?**  
Naopak: zraniteľnosť XSS vyradí z funkcie každú ochranu pred CSRF, pretože votrelecký kód môže v mene stránky čítať platné tokeny a posielať ich so sebou. Obe slabiny sa musia odstraňovať oddelene.

## Zdroje

- [OWASP Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
- [Príručka PHP: konfigurácia relácií](https://www.php.net/manual/de/session.configuration.php)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
