Röviden: A CSRF-et PHP-ban úgy előzi meg, hogy minden olyan kérésnek, amely valamit módosít, egy titkos, a sessionhöz kötött tokent kell magával hoznia, amelyet a szerver oldalán a hash_equals() függvénnyel ellenőriz. Emellett ellenőrzi az Origin vagy a Sec-Fetch-Site fejlécet, és a session-sütiket SameSite beállítással látja el. A GET-kérések soha nem módosíthatnak adatot, és a bejelentkezési űrlapnak is védelem kell.
Egy kattintás, amelyet senki sem vesz észre
Egy munkatársnő be van jelentkezve a webshop backendjébe, és egy második lapon megnyit egy idegen oldalt. Ez az oldal a háttérben űrlapot küld a backendnek, például egy e-mail-cím módosításához vagy egy fiók létrehozásához. A böngésző automatikusan hozzáfűzi a session-sütit. Az alkalmazás számára a kérés úgy néz ki, mint bármelyik másik a saját csapatból.
Ez a Cross-Site Request Forgery, röviden CSRF: egy kérés, amelyet a bejelentkezett felhasználó tudtán kívül küld egy olyan alkalmazásnak, amelyben megbízik. A támadó nem látja a választ, de nincs is rá szüksége. Elég, hogy a művelet végrehajtódik.
A CSRF nem szerepel a címlapokon, mint a PHP2Shell, de a saját fejlesztésű PHP-alkalmazásokban gyakori megállapítás. Az olyan keretrendszerek, mint a Symfony és a Laravel, védelmet hoznak magukkal; a rések ott főleg ott keletkeznek, ahol egyes útvonalaknál kikapcsolták. A keretrendszer nélküli saját fejlesztéseknél gyakran nincs is.
CSRF PHP-ban: mely kéréseket kell védeni
Védelmet igényel minden olyan kérés, amely állapotot változtat: mentés, törlés, rendelés, jogok kiosztása, beállítások módosítása, fájlok feltöltése, kijelentkezés. Az olvasó hívások nem igényelnek tokent, amíg valóban csak olvasnak.
Ebből következik az első szabály: Nincs módosítás GET-tel. Az olyan linkeket, mint a loeschen.php?id=…, idegen oldalak különösen könnyen kiválthatják, például egy beágyazott képen keresztül. A módosító műveletek POST, PUT, PATCH vagy DELETE metódusba valók.
A második szabály: A bejelentkezést is védeni kell. Az úgynevezett login-CSRF esetén az áldozatot észrevétlenül a támadó egy fiókjába léptetik be. Ha ott fizetési adatokat vagy dokumentumokat ad meg, azok a támadóhoz kerülnek. A bejelentkezési űrlapban lévő token és a sikeres bejelentkezés utáni új session-azonosító ezt megakadályozza.
A synchronizer token minta PHP-ban
A munkamenetekkel dolgozó klasszikus alkalmazásokhoz az OWASP a synchronizer token mintát ajánlja: a szerver véletlenszerű tokent generál, elmenti a sessionben, és minden módosító űrlapban visszavárja. Fontos, hogy a tokent az űrlapban vagy egy saját fejlécben küldjék el, nem sütiben.
<?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') ?>">
<!-- további mezők -->
</form>
A hash_equals() állandó időben hasonlít, így a válaszidő semmit sem árul el a helyes értékről. A bejelentkezés után a session-azonosítót újítsa meg a session_regenerate_id(true) függvénnyel, és hozzon létre új tokent. Hogy a random_bytes() miért a helyes forrás, és mely függvények alkalmatlanok, azt a Biztonságos véletlenszámok és tokenek PHP-ban magyarázza el.
A legtöbb alkalmazásnál elég egy token sessionönként. Az űrlaponkénti vagy kérésenkénti tokenek kissé növelik a védelmet, de gondokat okoznak több nyitott lapnál és a vissza gombnál. A váltás gyakoriságánál fontosabb, hogy a token soha ne jelenjen meg URL-ekben, ahol naplókba, a böngészőelőzményekbe és a Referrer-adatokon keresztül idegen oldalakhoz kerülhet.
A munkamenet nélküli, állapotmentes alkalmazásokhoz az OWASP alternatívaként az aláírt double-submit sütit említi: egy tokent, amelyet HMAC köt a munkamenethez. Az egyszerű, aláíratlan változatot kifejezetten nem ajánlja.
Második réteg: Origin, Sec-Fetch-Site és SameSite
A modern böngészők a kéréseknél információt küldenek az eredetükről. Az Origin fejléc megnevezi a hívó oldalt, a Sec-Fetch-Site pedig megmondja, hogy a kérés ugyanarról az oldalról (same-origin), egy rokon oldalról (same-site) vagy egy idegenről (cross-site) származik-e. Ebből egyszerű kiegészítő ellenőrzés építhető:
<?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;
}
Az OWASP itt két pontot hangsúlyoz: az eredetet teljes egészében kell összehasonlítani, hogy a hasonlóan hangzó domainek ne csússzanak át, és egy hiányzó fejléc vagy a null érték nem számíthat saját eredetnek. Mivel a régebbi böngészők nem mindig küldik el a fejléceket, a token marad a fő védelem.
A harmadik réteg a SameSite beállítású session-sütik. A PHP a session.cookie_samesite beállítást a 7.3-as verzió óta támogatja, az alapértelmezett érték üres. Állítson be legalább Lax értéket, tisztán backendeknél gyakran a Strict is lehetséges. A többi süti-beállítást a A sessionök biztonságossá tétele PHP-ban útmutató írja le.
Ha CMS-t használ, az többnyire saját eszközöket hoz magával. A WordPress például úgynevezett noncekkal dolgozik, amelyeket a bővítmények olyan függvényekkel építenek be, mint a wp_nonce_field() és a check_admin_referer(). A gyakorlatban a rések főleg azokban a bővítményekben keletkeznek, amelyek egyes műveleteknél elfelejtik ezeket az ellenőrzéseket. Ezért különösen a saját írású vagy kevéssé elterjedt bővítményeket ellenőrizze.
A más domainekről a böngészőből használt interfészeknél emellett a CORS-konfiguráció dönt. Ha a szerver minden kérő eredetet engedélyezettként ad vissza, és közben sütiket is megenged, maga iktatja ki a böngésző védelmét. Az OWASP azt ajánlja, hogy a CORS-t csak kifejezetten megbízható eredetekre nyissák meg; az összes aldomain átalánydíjas engedélyezése akkor válik problémává, ha valamelyik idegen kézbe kerül.
Ellenőrzőlista meglévő alkalmazásokhoz
Gyarapodott projekteknél a szisztematikus átvizsgálás segít:
- Felvenni az összes útvonalat és szkriptet, amely adatot módosít, az Ajax-végpontokat és a fájlfeltöltéseket is.
- Ellenőrizni, hogy használnak-e hozzá GET-et, és átállítani POST-ra vagy megfelelő metódusokra.
- Minden módosító útvonalnál bizonyítani, hogy tokent ellenőriznek; a keretrendszer kivételeit célzottan keresni.
- A bejelentkezést, a kijelentkezést és a jelszómódosítást külön tesztelni.
- A
session.cookie_samesitebeállítást és az eredetellenőrzést kiegészíteni. - Megszüntetni az XSS-réseket, mert a beinjektált kód bármely tokent elolvashat. Az eljárást a Cross-site scripting megelőzése PHP-ban írja le.
Az a CSRF-védelem, amelyet csak a frontenden JavaScripttel ellenőriznek, hatástalan. Az ellenőrzésnek a szerveren kell megtörténnie, mielőtt a művelet végrehajtódik.
Hogyan segít konkrétan a PHP SOS?
- Felmérjük a PHP-alkalmazása összes módosító útvonalát, és ellenőrizzük, hol hiányzik vagy van kikapcsolva a token.
- Központi ellenőrzési pontot építünk be ahelyett, hogy a tokenellenőrzéseket sok szkriptre szórnánk szét.
- Kiegészítjük az eredetellenőrzést és a SameSite-beállításokat anélkül, hogy a fizetési vagy bejelentkezési folyamatokat zavarnánk.
- Az átállítás után újra tesztelünk, és dokumentáljuk az eredményeket.
Az alkalmazás teljes körű ellenőrzését a biztonsági audit és penetrációs teszt szolgáltatás írja le.
Gyakori kérdések
Elég a SameSite=Lax CSRF-védelemként?
Nem. Az OWASP szerint a SameSite sütik további réteget jelentenek, nem pótlást. A Lax a sütiket továbbra is elküldi a normál, GET-tel történő oldalmegnyitásoknál, és ugyanazon weboldal aldomainjeiről érkező támadásokat nem fedi le.
Kell CSRF-token a JSON-API-khoz?
Ha az API sütikkel hitelesít, igen, vagy ezzel egyenértékű ellenőrzés. Alternatíva a saját kérés-fejlécek használata, amelyeket a böngészők idegen oldalaknál csak CORS-előellenőrzés után engednek meg. Ilyenkor a CORS-engedélybe viszont csak kifejezetten megbízható eredetek kerülhetnek.
A bejelentkezési űrlapot is védeni kell?
Igen. A login-CSRF esetén az áldozatot észrevétlenül egy idegen fiókba léptetik be, és ott a saját adatait adja meg. A bejelentkezési űrlapban lévő token és a bejelentkezés utáni új session ezt megakadályozza.
Véd a CSRF-védelem az XSS ellen is?
Fordítva: egy XSS-rés minden CSRF-védelmet kijátszik, mert a beinjektált kód az oldal nevében érvényes tokeneket olvashat és küldhet el. Mindkét sebezhetőséget külön kell megszüntetni.