# Ochrana proti CSRF v aplikacích PHP

> Ochrana proti CSRF v PHP: synchronizer token, kontrola Origin a Sec-Fetch-Site, cookies SameSite a login CSRF. S ukázkou kódu a kontrolním seznamem.

Zdroj: https://phpsos.de/cs/blog/csrf-schutz-php/  
Stav: 25.10.2026  
Poskytovatel: PHP SOS, Nouzová pomoc pro PHP a bezpečnost serverů. Okamžitá pomoc přes WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20den%2C%20PHP%20SOS%2C%20pot%C5%99ebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20PHP%20serverem.%20Firma%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručně:** CSRF v PHP zabráníte tak, že každý požadavek, který něco mění, musí přinést tajný token vázaný na relaci, který na serveru ověříte pomocí `hash_equals()`. Dále kontrolujete hlavičky `Origin` nebo `Sec-Fetch-Site` a cookies relace nastavujete s `SameSite`. Požadavky GET nesmějí nikdy měnit data a ochranu potřebuje i přihlašovací formulář.

## Kliknutí, kterého si nikdo nevšimne

Zaměstnankyně je přihlášena do backendu e-shopu a ve druhé záložce otevře cizí stránku. Ta na pozadí odešle backendu formulář, například ke změně e-mailové adresy nebo založení účtu. Prohlížeč automaticky připojí cookie relace. Pro aplikaci vypadá požadavek jako každý jiný z vlastního týmu.

To je Cross-Site Request Forgery, zkráceně CSRF: požadavek, který přihlášený uživatel nevědomky odešle aplikaci, které důvěřuje. Útočník odpověď nevidí, ale ani ji nepotřebuje. Stačí, že se akce provede.

CSRF není v titulcích jako PHP2Shell, ale ve vlastních aplikacích v PHP je to častý nález. Frameworky jako Symfony a Laravel ochranu obsahují; mezery vznikají hlavně tam, kde byla pro jednotlivé routy vypnuta. Vlastní vývoj bez frameworku často nemá žádnou.

## CSRF v PHP: které požadavky je třeba chránit

Ochranu potřebují všechny požadavky, které mění stav: ukládání, mazání, objednávání, přidělování práv, změna nastavení, nahrávání souborů, odhlášení. Čtecí požadavky token nepotřebují, pokud skutečně jen čtou.

Z toho plyne první pravidlo: **Žádná změna přes GET.** Odkazy jako `loeschen.php?id=…` lze z cizích stránek vyvolat obzvlášť snadno, například přes vložený obrázek. Měnící akce patří do POST, PUT, PATCH nebo DELETE.

Druhé pravidlo: **Chraňte i přihlášení.** U takzvaného login CSRF je oběť nepozorovaně přihlášena do účtu útočníka. Pokud tam zadá platební údaje nebo dokumenty, skončí u útočníka. Token v přihlašovacím formuláři a nové ID relace po úspěšném přihlášení tomu zabrání.

## Vzor synchronizer token v PHP

Pro klasické aplikace s relacemi doporučuje OWASP vzor synchronizer token: server vytvoří náhodný token, uloží ho do relace a očekává ho zpět v každém měnícím formuláři. Důležité je, aby se token posílal ve formuláři nebo ve vlastní hlavičce, ne 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') ?>">
    <!-- další pole -->
</form>
```

`hash_equals()` porovnává v konstantním čase, takže doba odpovědi neprozradí nic o správné hodnotě. Po přihlášení byste měli ID relace obnovit pomocí `session_regenerate_id(true)` a vytvořit nový token. Proč je `random_bytes()` správným zdrojem a které funkce se nehodí, vysvětluje článek [Bezpečná náhodná čísla a tokeny v PHP](https://phpsos.de/cs/blog/sichere-tokens-php/).

Pro většinu aplikací stačí jeden token na relaci. Tokeny pro každý formulář nebo požadavek ochranu mírně zvyšují, ale vedou k problémům s více otevřenými záložkami a tlačítkem Zpět. Důležitější než četnost změny je, aby se token nikdy neobjevil v URL, kde může skončit v lozích, v historii prohlížeče a v údajích Referrer pro cizí stránky.

Pro bezstavové aplikace bez relace uvádí OWASP jako alternativu podepsané double-submit cookie: token vázaný na sezení pomocí HMAC. Jednoduchá, nepodepsaná varianta se výslovně nedoporučuje.

## Druhá vrstva: Origin, Sec-Fetch-Site a SameSite

Moderní prohlížeče posílají u požadavků informace o svém původu. Hlavička `Origin` uvádí volající stránku, `Sec-Fetch-Site` říká, zda požadavek pochází ze stejné stránky (`same-origin`), příbuzné (`same-site`) nebo cizí (`cross-site`). Z toho lze sestavit jednoduchou doplňkovou kontrolu:

```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 zdůrazňuje dva body: původ se musí porovnávat celý, aby neprošly podobně znějící domény, a chybějící hlavička nebo hodnota `null` se nesmí považovat za vlastní původ. Protože starší prohlížeče hlavičky nevždy posílají, zůstává hlavní obranou token.

Třetí vrstvou jsou cookies relace s `SameSite`. PHP podporuje `session.cookie_samesite` od verze 7.3, výchozí hodnota je prázdná. Nastavte alespoň `Lax`, pro čisté backendy je často možné `Strict`. Ostatní nastavení cookies popisuje příručka [Zabezpečení relací v PHP](https://phpsos.de/cs/ratgeber/session-sicherheit-php/).

Pokud používáte CMS, obvykle přináší vlastní nástroje. WordPress například pracuje s takzvanými nonces, které pluginy zapojují pomocí funkcí jako `wp_nonce_field()` a `check_admin_referer()`. Mezery vznikají v praxi hlavně v rozšířeních, která takové kontroly u jednotlivých akcí zapomenou. Zkontrolujte proto zvlášť vlastnoručně psané nebo málo rozšířené pluginy.

U rozhraní, která prohlížeče používají z jiných domén, rozhoduje navíc konfigurace CORS. Pokud server vrací každý žádající původ jako povolený a zároveň povoluje cookies, sám vyřadí ochranu prohlížeče. OWASP doporučuje otevřít CORS jen pro výslovně důvěryhodné původy; plošné povolení všech subdomén se stává problémem, jakmile se jedna z nich dostane do cizích rukou.

## Kontrolní seznam pro existující aplikace

U vyrostlých projektů pomáhá systematická prohlídka:

1. Zachyťte všechny routy a skripty, které mění data, včetně koncových bodů Ajax a nahrávání souborů.
2. Zkontrolujte, zda se pro ně používá GET, a převeďte je na POST nebo vhodné metody.
3. U každé měnící routy doložte, že se token ověřuje; cíleně hledejte výjimky ve frameworku.
4. Přihlášení, odhlášení a změnu hesla otestujte samostatně.
5. Doplňte `session.cookie_samesite` a kontrolu původu.
6. Odstraňte zranitelnosti XSS, protože vložený kód může přečíst každý token. Postup popisuje stránka [Prevence Cross-Site Scripting v PHP](https://phpsos.de/cs/ratgeber/xss-php/).

> **Důležité:**
> Ochrana CSRF, která se kontroluje jen ve frontendu pomocí JavaScriptu, je bez účinku. Kontrola musí proběhnout na serveru dříve, než se akce provede.

## Jak konkrétně pomáhá PHP SOS?

- Zaznamenáme všechny měnící routy vaší aplikace v PHP a zkontrolujeme, kde token chybí nebo byl vypnut.
- Zavedeme centrální kontrolní místo místo rozprostření kontrol tokenu do mnoha skriptů.
- Doplníme kontrolu původu a nastavení SameSite, aniž bychom narušili platební nebo přihlašovací procesy.
- Po změně znovu otestujeme a výsledky zdokumentujeme.

Úplnou kontrolu vaší aplikace popisuje služba [Bezpečnostní audit a penetrační test](https://phpsos.de/cs/leistungen/sicherheitsaudit-penetrationstest/).


## Časté dotazy

**Stačí SameSite=Lax jako ochrana proti CSRF?**  
Ne. Cookies SameSite jsou podle OWASP další vrstva, ne náhrada. Lax dál posílá cookies při běžných zobrazeních stránky metodou GET a útoky ze subdomén téhož webu nepokrývá.

**Potřebují API s JSON token CSRF?**  
Pokud API ověřuje pomocí cookies, ano, nebo rovnocennou kontrolu. Alternativou jsou vlastní hlavičky požadavku, které prohlížeče u cizích stránek povolí jen po předběžné kontrole CORS. Povolení CORS pak ale smí obsahovat pouze výslovně důvěryhodné původy.

**Musí být chráněn i přihlašovací formulář?**  
Ano. U login CSRF je oběť nepozorovaně přihlášena do cizího účtu a zadává tam vlastní data. Token v přihlašovacím formuláři a nová relace po přihlášení tomu zabrání.

**Chrání ochrana CSRF i před XSS?**  
Naopak: zranitelnost XSS vyřadí jakoukoli ochranu CSRF, protože vložený kód může jménem stránky číst platné tokeny a posílat je s požadavkem. Obě slabiny je nutné odstranit samostatně.

## Zdroje

- [OWASP Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
- [Příručka PHP: konfigurace relací](https://www.php.net/manual/de/session.configuration.php)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
