# Zaščita pred CSRF v aplikacijah PHP

> Zaščita pred CSRF v PHP: sinhronizacijski žeton, preverjanje Origin in Sec-Fetch-Site, piškotki SameSite in CSRF ob prijavi. S primerom kode in kontrolnim seznamom.

Vir: https://phpsos.de/sl/blog/csrf-schutz-php/  
Stanje: 25.10.2026  
Ponudnik: PHP SOS, Nujna pomoč za PHP in varnost strežnikov. Takojšnja pomoč po WhatsAppu: https://wa.me/491736494914?text=Pozdravljeni%2C%20PHP%20SOS%2C%20potrebujemo%20pomo%C4%8D%20z%20na%C5%A1im%20stre%C5%BEnikom%20PHP.%20Podjetje%3A%20%E2%80%A6%20Spletna%20stran%3A%20%E2%80%A6

**Na kratko:** CSRF v PHP preprečite tako, da mora vsaka zahteva, ki kaj spreminja, prinesti skrivni žeton, vezan na sejo, ki ga na strežniku preverite s `hash_equals()`. Dodatno preverite glavi `Origin` ali `Sec-Fetch-Site` in piškotke seje nastavite s `SameSite`. Zahteve GET nikoli ne smejo spreminjati podatkov, zaščito pa potrebuje tudi prijavni obrazec.

## Klik, ki ga nihče ne opazi

Zaposlena je prijavljena v zaledje spletne trgovine in v drugem zavihku odpre tujo stran. Ta stran v ozadju pošlje obrazec v zaledje, na primer za spremembo e-poštnega naslova ali ustvarjanje računa. Brskalnik samodejno pripne piškotek seje. Za aplikacijo zahteva izgleda kot vsaka druga iz lastne ekipe.

To je Cross-Site Request Forgery, na kratko CSRF: zahteva, ki jo prijavljen uporabnik nevede pošlje aplikaciji, ki ji zaupa. Napadalec odgovora ne vidi, a ga tudi ne potrebuje. Dovolj je, da se dejanje izvede.

CSRF ni v naslovih kot PHP2Shell, a je v samostojno razvitih aplikacijah PHP pogosta ugotovitev. Ogrodja, kot sta Symfony in Laravel, prinašajo zaščito; ranljivosti nastanejo predvsem tam, kjer je bila za posamezne poti izklopljena. Lastni razvoj brez ogrodja pogosto nima nobene.

## CSRF v PHP: katere zahteve je treba zaščititi

Zaščito potrebujejo vse zahteve, ki spreminjajo stanje: shranjevanje, brisanje, naročanje, dodeljevanje pravic, spreminjanje nastavitev, nalaganje datotek, odjava. Bralni klici žetona ne potrebujejo, dokler res samo berejo.

Iz tega sledi prvo pravilo: **nobene spremembe prek GET.** Povezave, kot je `loeschen.php?id=…`, lahko tuje strani sprožijo posebej lahko, na primer prek vdelane slike. Spreminjajoča dejanja sodijo v POST, PUT, PATCH ali DELETE.

Drugo pravilo: **zaščitite tudi prijavo.** Pri t. i. CSRF ob prijavi je žrtev neopazno prijavljena v napadalčev račun. Če tam vnese podatke za plačilo ali dokumente, končajo pri napadalcu. Žeton v prijavnem obrazcu in nov ID seje po uspešni prijavi to preprečita.

## Vzorec sinhronizacijskega žetona v PHP

Za klasične aplikacije s sejami OWASP priporoča vzorec sinhronizacijskega žetona: strežnik ustvari naključen žeton, ga shrani v sejo in ga pričakuje nazaj v vsakem spreminjajočem obrazcu. Pomembno je, da se žeton pošlje v obrazcu ali v lastni glavi, ne v piškotku.

```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') ?>">
    <!-- nadaljnja polja -->
</form>
```

`hash_equals()` primerja v konstantnem času, tako da čas odgovora ne izda pravilne vrednosti. Po prijavi naj ID seje obnovite s `session_regenerate_id(true)` in ustvarite nov žeton. Zakaj je `random_bytes()` pravi vir in katere funkcije so neprimerne, pojasnjuje [Varna naključna števila in žetoni v PHP](https://phpsos.de/sl/blog/sichere-tokens-php/).

Za večino aplikacij zadošča en žeton na sejo. Žetoni na obrazec ali na zahtevo zaščito nekoliko povečajo, a vodijo do težav z več odprtimi zavihki in gumbom Nazaj. Pomembnejše od pogostosti menjave je, da se žeton nikoli ne pojavi v URL-jih, kjer lahko konča v dnevnikih, v zgodovini brskalnika in v podatkih Referrer, poslanih tujim stranem.

Za aplikacije brez stanja in brez seje OWASP kot alternativo navaja podpisani piškotek Double Submit: žeton, ki je z HMAC vezan na sejo. Preprosta, nepodpisana različica se izrecno ne priporoča.

## Druga plast: Origin, Sec-Fetch-Site in SameSite

Sodobni brskalniki pri zahtevah pošiljajo podatke o svojem izvoru. Glava `Origin` navaja klicočo stran, `Sec-Fetch-Site` pove, ali zahteva prihaja z iste strani (`same-origin`), sorodne (`same-site`) ali tuje (`cross-site`). Iz tega je mogoče sestaviti preprosto dodatno preverjanje:

```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 pri tem poudarja dve točki: izvor je treba primerjati v celoti, da podobno zveneče domene ne zdrsnejo skozi, manjkajoča glava ali vrednost `null` pa ne sme veljati za lasten izvor. Ker starejši brskalniki glav ne pošiljajo vedno, ostane glavna obramba žeton.

Tretja plast so piškotki seje s `SameSite`. PHP podpira `session.cookie_samesite` od različice 7.3, privzeta vrednost je prazna. Nastavite vsaj `Lax`, za čista zaledja je pogosto mogoč `Strict`. Preostale nastavitve piškotkov opisuje vodnik [Zavarovanje sej v PHP](https://phpsos.de/sl/ratgeber/session-sicherheit-php/).

Če uporabljate CMS, običajno prinese lastna orodja. WordPress na primer dela s t. i. noncami, ki jih vtičniki vključijo s funkcijami, kot sta `wp_nonce_field()` in `check_admin_referer()`. Ranljivosti v praksi nastanejo predvsem v razširitvah, ki pri posameznih dejanjih takšna preverjanja pozabijo. Zato posebej preverite sami napisane ali manj razširjene vtičnike.

Pri vmesnikih, ki jih brskalniki uporabljajo z drugih domen, dodatno odloča konfiguracija CORS. Če strežnik vsak zahtevajoči izvor vrne kot dovoljen in hkrati dovoli piškotke, sam izniči zaščito brskalnika. OWASP priporoča, da se CORS odpre samo za izrecno zaupanja vredne izvore; splošno dovoljenje za vse poddomene postane težava, brž ko ena od njih pride v tuje roke.

## Kontrolni seznam za obstoječe aplikacije

Pri zraslih projektih pomaga sistematičen pregled:

1. Zajemite vse poti in skripte, ki spreminjajo podatke, tudi končne točke Ajax in nalaganje datotek.
2. Preverite, ali se zanje uporablja GET, in preidite na POST ali ustrezne metode.
3. Za vsako spreminjajočo pot dokažite, da se žeton preverja; izjeme v ogrodju iščite ciljno.
4. Prijavo, odjavo in spremembo gesla preizkusite posebej.
5. Dodajte `session.cookie_samesite` in preverjanje izvora.
6. Odpravite ranljivosti XSS, ker lahko vnesena koda prebere vsak žeton. Postopek opisuje [Preprečevanje cross-site scriptinga v PHP](https://phpsos.de/sl/ratgeber/xss-php/).

> **Pomembno:**
> Zaščita pred CSRF, ki se preverja samo v frontendu z JavaScriptom, je neučinkovita. Preverjanje mora potekati na strežniku, preden se dejanje izvede.

## Kako konkretno pomaga PHP SOS?

- Zajamemo vse spreminjajoče poti vaše aplikacije PHP in preverimo, kje žeton manjka ali je bil izklopljen.
- Vgradimo osrednjo točko preverjanja, namesto da bi preverjanja žetonov razpršili po številnih skriptah.
- Dodamo preverjanje izvora in nastavitve SameSite, ne da bi motili postopke plačila ali prijave.
- Po prehodu znova preizkusimo in dokumentiramo rezultate.

Popoln pregled vaše aplikacije opisuje storitev [Varnostni pregled in penetracijski test](https://phpsos.de/sl/leistungen/sicherheitsaudit-penetrationstest/).


## Pogosta vprašanja

**Ali SameSite=Lax zadošča kot zaščita pred CSRF?**  
Ne. Piškotki SameSite so po OWASP dodatna plast, ne nadomestilo. Lax piškotke še vedno pošlje pri običajnih klicih strani z GET, napadi s poddomen iste spletne strani pa niso pokriti.

**Ali API-ji JSON potrebujejo žeton CSRF?**  
Če API overja s piškotki, da ali enakovredno preverjanje. Druga možnost so lastne glave zahtev, ki jih brskalniki pri tujih straneh dovolijo šele po predhodnem preverjanju CORS. Takrat sme dovoljenje CORS vsebovati samo izrecno zaupanja vredne izvore.

**Ali je treba zaščititi tudi prijavni obrazec?**  
Da. Pri CSRF ob prijavi je žrtev neopazno prijavljena v tuj račun in tam vnaša lastne podatke. Žeton v prijavnem obrazcu in nova seja po prijavi to preprečita.

**Ali zaščita pred CSRF ščiti tudi pred XSS?**  
Obratno: ranljivost XSS izniči vsako zaščito pred CSRF, ker lahko vnesena koda v imenu strani prebere veljavne žetone in jih pošlje zraven. Obe šibkosti je treba odpraviti ločeno.

## Viri

- [OWASP Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
- [Priročnik PHP: konfiguracija sej](https://www.php.net/manual/de/session.configuration.php)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
