# CSRF zaštita u PHP aplikacijama

> Provedba CSRF zaštite u PHP-u: Synchronizer token, provjera Origin i Sec-Fetch-Site, SameSite kolačići i login CSRF. S primjerom koda i kontrolnim popisom.

Izvor: https://phpsos.de/hr/blog/csrf-schutz-php/  
Stanje: 25.10.2026  
Pružatelj: PHP SOS, PHP hitna pomoć i sigurnost poslužitelja. Hitna pomoć putem WhatsAppa: https://wa.me/491736494914?text=Pozdrav%20PHP%20SOS%2C%20trebamo%20pomo%C4%87%20s%20na%C5%A1im%20PHP%20poslu%C5%BEiteljem.%20Tvrtka%3A%20%E2%80%A6%20Web-stranica%3A%20%E2%80%A6

**Ukratko:** CSRF u PHP-u sprječavate tako da svaki zahtjev koji nešto mijenja mora sa sobom donijeti tajni token vezan uz sesiju, koji na poslužitelju provjeravate pomoću `hash_equals()`. Uz to provjeravate zaglavlja `Origin` ili `Sec-Fetch-Site` i postavljate kolačiće sesije s `SameSite`. GET zahtjevi nikad ne smiju mijenjati podatke, a i obrazac za prijavu treba zaštitu.

## Klik koji nitko ne primijeti

Zaposlenica je prijavljena u backend shopa i u drugoj kartici otvara tuđu stranicu. Ta stranica u pozadini šalje obrazac backendu, primjerice za promjenu adrese e-pošte ili stvaranje računa. Preglednik automatski dodaje kolačić sesije. Aplikaciji zahtjev izgleda kao i svaki drugi iz vlastitog tima.

To je Cross-Site Request Forgery, skraćeno CSRF: zahtjev koji prijavljeni korisnik nesvjesno šalje aplikaciji u koju ima povjerenja. Napadač ne vidi odgovor, ali mu nije ni potreban. Dovoljno je da se radnja izvrši.

CSRF se ne pojavljuje u naslovima kao PHP2Shell, ali je u PHP aplikacijama razvijenim u vlastitoj izvedbi čest nalaz. Frameworci poput Symfonyja i Laravela donose zaštitu; ranjivosti nastaju ponajprije tamo gdje je za pojedine rute isključena. Vlastite izvedbe bez frameworka često je uopće nemaju.

## CSRF u PHP-u: koje zahtjeve treba zaštititi

Zaštitu trebaju svi zahtjevi koji mijenjaju stanje: spremanje, brisanje, naručivanje, dodjela prava, promjena postavki, upload datoteka, odjava. Pozivi koji samo čitaju ne trebaju token, sve dok doista samo čitaju.

Iz toga proizlazi prvo pravilo: **Nikakve promjene putem GET-a.** Poveznice poput `loeschen.php?id=…` tuđe stranice mogu osobito lako pokrenuti, primjerice preko ugrađene slike. Radnje koje mijenjaju pripadaju u POST, PUT, PATCH ili DELETE.

Drugo pravilo: **Zaštititi i prijavu.** Kod takozvanog login CSRF-a žrtva se neprimjetno prijavi u račun napadača. Ako tamo unese podatke za plaćanje ili dokumente, oni završavaju kod napadača. Token u obrascu za prijavu i novi ID sesije nakon uspješne prijave to sprječavaju.

## Uzorak Synchronizer tokena u PHP-u

Za klasične aplikacije sa sesijama OWASP preporučuje uzorak Synchronizer tokena: poslužitelj stvara slučajni token, sprema ga u sesiju i očekuje ga natrag u svakom obrascu koji mijenja podatke. Važno je da se token šalje u obrascu ili u vlastitom zaglavlju, a ne u kolačiću.

```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') ?>">
    <!-- ostala polja -->
</form>
```

`hash_equals()` uspoređuje u konstantnom vremenu, pa vrijeme odgovora ne otkriva ništa o ispravnoj vrijednosti. Nakon prijave trebali biste obnoviti ID sesije pomoću `session_regenerate_id(true)` i stvoriti novi token. Zašto je `random_bytes()` ispravan izvor i koje su funkcije neprikladne, objašnjava članak [Sigurni slučajni brojevi i tokeni u PHP-u](https://phpsos.de/hr/blog/sichere-tokens-php/).

Jedan token po sesiji dovoljan je za većinu aplikacija. Tokeni po obrascu ili po zahtjevu malo povećavaju zaštitu, ali dovode do problema s više otvorenih kartica i gumbom za povratak. Važnije od učestalosti promjene je da se token nikad ne pojavljuje u URL-ovima, gdje može završiti u zapisnicima, povijesti preglednika i u podacima o referreru prema tuđim stranicama.

Za aplikacije bez stanja i bez sesije OWASP kao alternativu navodi potpisani Double-Submit kolačić: token vezan za sesiju pomoću HMAC-a. Jednostavna, nepotpisana varijanta izričito se ne preporučuje.

## Drugi sloj: Origin, Sec-Fetch-Site i SameSite

Moderni preglednici pri zahtjevima šalju podatke o svojem podrijetlu. Zaglavlje `Origin` navodi stranicu koja poziva, a `Sec-Fetch-Site` kaže dolazi li zahtjev s iste stranice (`same-origin`), srodne (`same-site`) ili tuđe (`cross-site`). Iz toga se može sastaviti jednostavna dodatna provjera:

```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 naglašava dvije točke: podrijetlo se mora uspoređivati u cijelosti kako domene sličnog zvuka ne bi prošle, a nedostajuće zaglavlje ili vrijednost `null` ne smiju vrijediti kao vlastito podrijetlo. Budući da stariji preglednici zaglavlja ne šalju uvijek, token ostaje glavna obrana.

Treći sloj su kolačići sesije s `SameSite`. PHP podržava `session.cookie_samesite` od verzije 7.3, a zadana vrijednost je prazna. Postavite najmanje `Lax`, za čiste backende često je moguć `Strict`. Ostale postavke kolačića opisuje vodič [Osigurati sesije u PHP-u](https://phpsos.de/hr/ratgeber/session-sicherheit-php/).

Ako koristite CMS, on obično donosi vlastite alate. WordPress, primjerice, radi s takozvanim nonceovima, koje dodaci uključuju funkcijama poput `wp_nonce_field()` i `check_admin_referer()`. Ranjivosti u praksi nastaju ponajprije u proširenjima koja takve provjere zaborave kod pojedinih radnji. Zato osobito provjerite vlastoručno napisane ili malo raširene dodatke.

Kod sučelja koja preglednici koriste s drugih domena dodatno odlučuje CORS konfiguracija. Ako poslužitelj svako podrijetlo koje šalje zahtjev vraća kao dopušteno, a istodobno dopušta kolačiće, sam poništava zaštitu preglednika. OWASP preporučuje CORS otvoriti samo za izričito pouzdana podrijetla; paušalno odobrenje svih poddomena postaje problem čim jedna od njih dospije u tuđe ruke.

## Kontrolni popis za postojeće aplikacije

Kod razvijenih projekata pomaže sustavan pregled:

1. Popisati sve rute i skripte koje mijenjaju podatke, uključujući Ajax krajnje točke i uploade datoteka.
2. Provjeriti koristi li se za to GET i prijeći na POST ili odgovarajuće metode.
3. Za svaku rutu koja mijenja dokazati da se token provjerava; iznimke u frameworku tražiti ciljano.
4. Zasebno testirati prijavu, odjavu i promjenu lozinke.
5. Dopuniti `session.cookie_samesite` i provjeru podrijetla.
6. Ukloniti XSS ranjivosti, jer ubačeni kod može pročitati svaki token. Postupak opisuje članak [Spriječiti Cross-Site Scripting u PHP-u](https://phpsos.de/hr/ratgeber/xss-php/).

> **Važno:**
> CSRF zaštita koja se provjerava samo u frontendu pomoću JavaScripta nema učinka. Provjera se mora odvijati na poslužitelju, prije izvršenja radnje.

## Kako PHP SOS konkretno pomaže?

- Bilježimo sve rute Vaše PHP aplikacije koje mijenjaju podatke i provjeravamo gdje token nedostaje ili je isključen.
- Ugrađujemo središnju točku provjere umjesto da se provjere tokena raspoređuju po mnogim skriptama.
- Dopunjujemo provjeru podrijetla i SameSite postavke, a da ne smetamo procesima plaćanja ili prijave.
- Nakon prijelaza ponovno testiramo i dokumentiramo rezultate.

Potpunu provjeru Vaše aplikacije opisuje usluga [Sigurnosni audit i penetracijski test](https://phpsos.de/hr/leistungen/sicherheitsaudit-penetrationstest/).


## Česta pitanja

**Je li SameSite=Lax dovoljan kao CSRF zaštita?**  
Ne. SameSite kolačići prema OWASP-u su dodatni sloj, a ne zamjena. Lax i dalje šalje kolačiće pri običnim pozivima stranica metodom GET, a napadi s poddomena iste web-stranice nisu pokriveni.

**Trebaju li JSON API-ji CSRF token?**  
Ako API autentificira pomoću kolačića, da, ili jednakovrijednu provjeru. Alternativa su vlastita zaglavlja zahtjeva koja preglednici s tuđih stranica dopuštaju tek nakon CORS prethodne provjere. Tada CORS odobrenje smije sadržavati samo izričito pouzdana podrijetla.

**Mora li se zaštititi i obrazac za prijavu?**  
Da. Kod login CSRF-a žrtva se neprimjetno prijavi u tuđi račun i tamo unosi vlastite podatke. Token u obrascu za prijavu i nova sesija nakon prijave to sprječavaju.

**Štiti li CSRF zaštita i od XSS-a?**  
Obrnuto: XSS ranjivost poništava svaku CSRF zaštitu, jer ubačeni kod u ime stranice može pročitati valjane tokene i poslati ih. Obje se ranjivosti moraju ukloniti zasebno.

## Izvori

- [OWASP Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
- [PHP priručnik: konfiguracija sesija](https://www.php.net/manual/de/session.configuration.php)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
