Co jsou SameSite cookie a proč je potřebujeme?

Publikováno: 27.7.2020

SameSite cookies poskytují mechanismus, jak rozpoznat, co vedlo k načtení stránky. Jestli to bylo prokliknutí odkazu na jiném webu, odeslání formuláře, načtení uvnitř iframe, pomocí JavaScriptu atd.

Celý článek

Text vyšel původně na autorově webu.

Rozlišit, jak byla stránka načtena, je naprosto zásadní kvůli bezpečnosti. Závažná zranitelnost Cross-Site Request Forgery (CSRF) je tu s námi už dlouhých dvacet let a teprve SameSite cookie nabízí systémovou cestu, jak ji řešit.

Útok CSRF spočívá v tom, že útočník naláká oběť na stránku, která nenápadně v prohlížeči oběti vykoná požadavek na webovou aplikaci, na které je oběť přihlášena, a ta se domnívá, že požadavek vykonala oběť o své vůli. A tak pod identitou oběti provede nějaký úkon, aniž by ta o tom věděla. Může jít o změnu nebo smazání dat, odeslání zprávy atd. Aby aplikace útoku zabránila, musí rozlišit, jestli požadavek vznikl povolenou cestou, např. odesláním formuláře v ní samotné, nebo nějak jinak. SameSite cookie tohle umí.

Jak to funguje? Řekněme, že mám web běžící na nějaké doméně a vytvořím na něm tři různé cookies s atributy SameSite=LaxSameSite=Strict a SameSite=None. Název ani hodnota nehrají roli. Prohlížeč si je uloží.

  1. Když libovolnou URL na mém webu otevřu přímým zadáním do adresního řádku nebo kliknutím na záložku, prohlížeč všechny tři cookie odešle.
  2. Když se na libovolnou URL na mém webu dostanu jakkoliv ze stránky z téhož webu, prohlížeč všechny tři cookie odešle.
  3. Když se na libovolnou URL na mém webu dostanu ze stránky z jiného webu, prohlížeč pošle jen cookie s atributem None a v určitých případech i Lax, viz tabulka:
Kód na jiném webuOdeslané cookie
Link<a href="…">None + Lax
Form GET<form method="GET" action="…">None + Lax
Form POST<form method="POST" action="…">None
iframe<iframe src="…">None
AJAX$.get('…'), fetch('…')None
Image<img src="…">None
Prefetch<link rel="prefetch" href="…">None
None

SameSite cookies dokáží rozlišit jen několik málo případů, ale jde právě o ty podstatné pro ochranu před CSRF.

Pokud mám třeba na webu v administraci formulář nebo nějaký odkaz pro smazání položky a ten byl odeslán/odkliknut, tak nepřítomnost cookie vytvořené s atributem Strict znamená, že se tak nestalo na mém webu, ale že požadavek přišel odjinud, tedy že jde o CSRF útok.

Cookie pro odhalení CSRF útoku vytvářejte jako tzv. session cookie bez atributu Expires, platnost je pak v podstatě nekonečná.

Doména vs site

„Na mém webu“ není to stejné jako „na mé doméně“, nejde o doménu, ale o web site (proto i název SameSite). Site sice často odpovídá doméně, ale třeba u služby github.io odpovídá subdoméně. Požadavek z doc.nette.org na files.nette.org je same-site, zatímco požadavek z nette.github.io na tracy.github.io je už cross-site. Tady je to hezky vysvětlené.

<iframe>

Z předchozích řádků již vyplynulo, že pokud je stránka z mého webu načtená uvnitř <iframe> na jiném webu, nepošle jí prohlížeč Strict ani Lax cookies. Je tu ale ještě jedna důležitá věc: pokud takto načtená stránka vytvoří Strict nebo Lax cookie, prohlížeč je ignoruje.

Tím vzniká možnost se bránit proti podvodnému získávání cookie neboli Cookie Stuffing, kde dosud systémová obrana taky chyběla. Trik spočívá v tom, že podvodník inkasuje provizi za affiliate marketing, ačkoliv uživatele na web obchodníka nepřivedl. Místo odkazu s affiliate ID, na který by musel uživatel kliknout, vloží do stránky neviditelný <iframe> se stejným odkazem a značkuje tak všechny návštěvníky.

Cookie bez atributu SameSite

Sušenky bez atributu SameSite se vždy posílaly při jakémkoliv same-site i cross-site požadavku. Stejně jako SameSite=None. Jenže v blízké budoucnosti začnou prohlížeče považovat příznak SameSite=Lax za výchozí, takže sušenky bez atributu budou považovány za Lax. Což je docela nebývale velký BC break v chování prohlížečů. Pokud chcete, aby se cookie i nadále chovala stejně a přenášela se při jakémkoliv cross-site požadavku, je potřeba jí nastavit SameSite=None. (Pokud nevyvíjíte embedované widgety apod., moc často to nechcete.) Bohužel pro loňské prohlížeče je hodnota None nečekaná. Safari 12 ji chápe jako Strict, takže na starších iOS a macOS vzniká ošemetný problém.

A ještě pozor: None funguje jen, když je nastaven s atributem Secure.

Co udělat při útoku?

Utéct! Základní pravidlo sebeobrany, jak v reálném životě, tak na webu. Obrovskou chybou spousty frameworků je, že při detekci CSRF útoku zobrazí formulář znovu a napíší něco jako „Token CSRF je neplatný. Zkuste prosím formulář znovu odeslat“. Tím, že jej uživatel odešle znovu, je útok dokonán. Taková ochrana postrádá smysl, když vlastně uživatele vyzvete, aby ji obešel.

Ještě nedávno dělal Chrome v případě cross-site požadavku to, že po refreshi stránku zobrazil znovu, ale tentokrát cookie s atributem Strict poslal. Takže refresh vyřadil ochranu před CSRF založenou na SameSite cookie. Dnes už to naštěstí nedělá, ale je možné, že to dělají jiné nebo starší prohlížeče. Uživatel také může stránku „refreshnout“ kliknutím na adresní řádek + enter, což se bere jako přímé zadání URL (bod 1) a všechny cookie se odešlou.

Takže při detekci CSRF je nejlepší přesměrovat s HTTP kódem 302 jinam, třeba na homepage. Zbavíte se tak nebezpečných POST dat a ani problematická URL se neuloží do historie.

Nekompatibility

SameSite dlouho nefungovalo ani zdaleka tak, jak by mělo. Především kvůli chybám v prohlížečích a nedostatkům ve specifikaci, která třeba vůbec neřešila přesměrování nebo refresh. Samesite cookie se nepřenášely třeba při uložení nebo tisku stránky, naopak se přenášely po refreshi, když zrovna neměly atd. Naštěstí dnes už je situace lepší. Mám za to, že z vážných nedostatků přetrvává v aktuálních verzích prohlížečů jen ten výše zmíněný u Safari.

Doplnění: kromě SameSite lze velmi čerstvě rozlišit původ požadavku i hlavičkou Origin, což je nástupce hlavičky Referer více respektující soukromí uživatelů a pravopis.

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace