A SameSite=Lax nem böngésző-egyezmény: a Firefox kipróbálta, aztán visszavonta

A Chrome négy éve úgy állítja be a sütiket, mintha SameSite=Lax lenne rajtuk – de a Firefox ezt kipróbálta, aztán dokumentáltan visszavonta, és a Safari sosem is akarta ugyanazt. Amikor az express-session cookie.sameSite alapértéke false, a CSRF-védelmed pontosan attól függ, melyik böngészőt használja a felhasználód.

Ha megkérdezed egy kollégát, miért nem állítja be explicit a SameSite attribútumot a session sütijén, valószínűleg azt mondja: „nem kell, a böngészők alapból Lax-ra állítják, ha nem adok meg semmit”. Ez a mondat Chrome-ban négy éve igaz. Firefoxban soha nem volt igaz, és a szándék, hogy igaz legyen, egy Mozilla bugzilla-tikettben van eltemetve, WONTFIX státusszal. Safari pedig egészen más háborút harcol, és nem is ugyanazzal a fegyverrel. Ha ez most meglepetés, pont ez a cikk témája.

Három sorompó három eltérő nyitási állásban, mellettük egy-egy csokis keksz sütit szimbolizálva: az egyik átjut, a másik félig, a harmadik el van torlaszolva

A sztori, amit mindenki ismer

2020 elején a Chrome 80 bevezette, hogy ha egy Set-Cookie fejlécben nincs SameSite attribútum, a böngésző úgy kezeli a sütit, mintha SameSite=Lax lenne rajta. A Chromium saját dokumentációja szépen le is írja a három értéket: Strict (csak same-site kérésekben megy ki a süti), Lax (same-site kérések plusz top-level navigáció, tehát ha a felhasználó rákattint egy linkre), és None (mindig kimegy, de kötelező mellé a Secure). Ez volt az az év, amikor a CSRF-védelem hirtelen „beépített” dologgá vált a fejlesztők fejében – nem kell semmit csinálni, a böngésző megcsinálja helyetted.

Csak hogy ez sosem volt ilyen egyszerű, még magában a Chrome-ban sem. A rollout részeként bevezettek egy ideiglenes kivételt, amit „Lax+POST”-nak hívnak: az újonnan létrehozott sütik, amelyeknek nincs explicit SameSite értékük, az első 2 percben még kimennek top-level cross-site POST kérésekkel is. Ezt kifejezetten azért csinálták, hogy ne törjenek el azonnal azok az SSO/OAuth bejelentkezési folyamatok, amelyek egy másik oldalról POST-tal küldenek vissza egy CSRF-tokent tartalmazó sütit. A dokumentáció ezt „ideiglenes” megoldásnak nevezi, ami „el lesz távolítva a jövőben” – ez a mondat ma is ott van a Chromium FAQ-jában, konkrét dátum nélkül, több mint hat évvel a Chrome 80 után. Szóval már a „tiszta” Lax-alapértelmezés bevezetése sem volt tiszta – volt benne egy kétperces hátsó kapu, amit senki nem dokumentál rendesen, mert soha nem kellett rá támaszkodni tudatosan.

A specifikáció szintjén ez a draft-ietf-httpbis-rfc6265bis munkaanyagban él, ami – érdekesség – 2026-ban is még csak Internet-Draft, nem RFC. Tehát amikor valaki azt mondja, hogy „a SameSite=Lax alapértelmezés szabvány”, technikailag egy hat éve nem lezárt draftra hivatkozik, amit egyébként pont a Chrome viselkedése alapján írtak meg utólag.

Amit kevesebben tudnak: a Firefox nem szállt be

2022 januárjában a Firefox 96 leszállította a Lax-by-default viselkedést. Ez a rész a sztorinak még oké. Ami nem közismert: pár hónapon belül visszavonták.

A Mozilla bugzilla #1617609 tiketten végigkövethető a teljes történet. A funkciót bevezették, majd komoly webkompatibilitási problémák miatt (a fejlesztői kommentek konkrétan az Amazont és a Binget említik érintett oldalként) visszaállították az eredeti viselkedésre. A tiket jelenlegi állapota RESOLVED WONTFIX, és az egyik legutóbbi maintainer-kommentben ez áll: a Firefox jelenleg nem implementál Lax-by-default viselkedést, ha nincs megadva SameSite, akkor None-t használnak, és „jelenleg nincs tervben, hogy 2025-ben újra megpróbálják”. Nem elavult infó egy régi blogposztból – ez a Mozilla saját issue-trackeréből származó, jelenleg is érvényes állapot.

Ezt független forrásból is megerősíti az OWASP SameSite lapja: 2024 decemberi állás szerint „a Lax a Chrome, az Edge és az Opera alapértelmezése, de nem a Firefoxé és nem is a Safarié”. Szóval nem arról van szó, hogy a böngészők lassan felzárkóznak – a Firefox tudatosan, dokumentáltan kimaradt belőle, és nincs jelzett terv a visszatérésre.

Ez azért számít, mert a SameSite gyakorlati értéke pont abban áll, hogy nem kell semmit csinálnod – ha viszont az egyik nagy böngészőmotor sosem csinálja meg helyetted, akkor a „nem kell semmit csinálnod” állítás hamis lett, csak senki nem mondta el újra hangosan négy évvel a bevezetés után.

A Safari egy teljesen másik játékot játszik

A Safari/WebKit nem is próbálta lekövetni ezt a spec-ötletet ugyanúgy. Az Intelligent Tracking Prevention (ITP) egy sokkal agresszívabb, heurisztika-alapú third-party cookie blokkolást csinál, ami jórészt függetlenül működik a SameSite értéktől – tehát nem az számít, hogy alapból Lax-ot vagy None-t ad-e a sütinek, hanem hogy a sütit egyáltalán engedi-e third-party kontextusban létezni.

Ehhez jön egy friss, kevésbé ismert adalék: a WKWebView-ban (ez az a motor, amin a legtöbb natív iOS app beágyazott böngészője megy) az iOS 18-cal csendben, dokumentálatlanul megváltozott az alapértelmezett SameSite viselkedés None-ról Lax-ra. Egy fejlesztő az Apple Developer Forumson írta le pontosan ezt: minden süti, amin nincs explicit SameSite, iOS 18 előtt None-ként, iOS 18-tól Lax-ként viselkedik – és ez a váltás „sehol nincs dokumentálva”. Ha valaha debuggoltál már egy natív appba ágyazott webes flow-t, ami „az egyik telefonon megy, a másikon nem”, ez pontosan az a fajta rejtett ok, amit sosem gyanítasz elsőre.

Amit ez a saját kódodban jelent

Ha Node/Express-t használsz session-kezeléshez, valószínűleg az express-session csomagot is. A csomag saját dokumentációja szerint a cookie.sameSite opció alapértéke false – ami nem azt jelenti, hogy „Lax”, hanem azt, hogy nincs SameSite attribútum a kimenő Set-Cookie fejlécben. Tehát ha nem állítottad be explicit, a session sütid CSRF-kitettsége pontosan attól függ, melyik böngészőt használja a felhasználó.

Nézzük meg, mit jelent ez a nyers fejléc szintjén:

function buildSetCookie(name, value, { sameSite } = {}) {
  const parts = [`${name}=${value}`, 'Path=/', 'HttpOnly'];
  if (sameSite) parts.push(`SameSite=${sameSite}`);
  if (sameSite === 'None') parts.push('Secure');
  return parts.join('; ');
}

// Ezt csinálja az express-session `cookie.sameSite: false` alapértelmezése:
console.log(buildSetCookie('connect.sid', 's%3Aabc123'));
// -> connect.sid=s%3Aabc123; Path=/; HttpOnly

// Ez az explicit, böngészőtől független verzió:
console.log(buildSetCookie('connect.sid', 's%3Aabc123', { sameSite: 'Lax' }));
// -> connect.sid=s%3Aabc123; Path=/; HttpOnly; SameSite=Lax

Az első sor Chrome-ban Lax-ként viselkedik, Firefoxban None-ként. Ugyanaz a fejléc, két teljesen eltérő biztonsági garancia, attól függően, hogy a felhasználód épp melyik böngészőben ül. A StatCounter friss, 2026 júliusi adatai szerint a Firefox globális részesedése kb. 3,3% – ez nem tűnik soknak, de ha van olyan production rendszered, ahol a session süti CSRF-védelme kizárólag a böngésző hallgatólagos alapértelmezésén nyugszik, akkor pontosan ennyi felhasználódnál nincs is meg az a védelem, amit feltételezel.

Mikor számít ez valójában

Ha csak GET-es, olvasás-jellegű navigációról van szó, a SameSite hiánya nem drámai – a Lax és a None között pont ott van a különbség, hogy state-changing kéréseknél (POST, PUT, DELETE) mit enged át cross-site kontextusból. Ott számít igazán: fizetés indítása, jelszóváltás, beállítás módosítása – minden olyan endpoint, amit egy támadó szeretne rávenni a böngésződet, hogy elküldjön helyetted.

Fontos, hogy a SameSite sosem volt hivatott önmagában helyettesíteni a CSRF-tokeneket – az OWASP is defense-in-depth réteget javasol, nem kizárólagos védelmet, épp azért, mert az egész mechanizmus a böngésző jóindulatán és konzisztenciáján alapul, és láttuk, hogy ez a konzisztencia elméleti. Ha van state-changing endpointod, amit külső oldalról POST-olhatnak, oda ma is kell explicit CSRF-token vagy legalább egy double-submit cookie pattern, függetlenül attól, hogy mit gondolsz a SameSite alapértelmezéséről.

Amit viszont mindenképp meg lehet és kell tenni: állítsd be a SameSite attribútumot explicit módon minden sütin, amit kiadsz. Ez egy sor kód, nulla trade-off, és pontosan azért kell megtenni, mert a böngészők nem egyeznek meg helyetted. Ha SameSite=None-t kell használnod (mert tényleg kell a süti cross-site kontextusban, pl. egy beágyazható widgethez), az mindig jár Secure-jal is – ezt már a specifikáció is kikényszeríti a modern böngészőkben.

Production-be én mindenképp explicit SameSite-tal mennék, és nem a böngésző jóindulatára bíznám – nem azért, hogy paranoiás legyek, hanem mert az egész eset pont azt mutatja meg, hogy egy négy éve „megoldottnak” hitt probléma valójában sosem volt egységesen megoldva, csak a Chrome dominanciája miatt úgy tűnt.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük