Aktuální situace: PHP2Shell. Dvě kritické zranitelnosti PHP (CVSS 9.2) umožňují spustit škodlivý kód bez přihlášení. Záplaty od 21. 10. 2026.Co nyní udělat →

Ochrana proti CSRF v aplikacích PHP

Jak vývojáři v aplikacích PHP zabraňují Cross-Site Request Forgery: tokeny pro každý měnící požadavek, kontrola původu v hlavičce a cookies SameSite jako další vrstva.

Stav 5 min čteníBezpečný vývoj

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
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.

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
$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.

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.
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.

Č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

Okamžitý kontakt

Každá hodina se počítá. Napište nám hned.

Stručně popište systém, verzi PHP a pozorování. Ozveme se, vyjasníme situaci a budeme pracovat na dálku, dokud nebude incident uzavřen a zdokumentován.

Okamžitá pomoc