Co jsou SameSite cookie a proč je potřebujeme?
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.
Rozlišit, jak byla stránka načtena, je totiž 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=Lax
,
SameSite=Strict
a SameSite=None
. Název ani hodnota
nehrají roli. Prohlížeč si je uloží.
- 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.
- 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.
- 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 iLax
, viz tabulka:
Kód na jiném webu | Odeslané 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.
Komentáře
ic #1
A co Origin hlavička? To mi přijde jako snazší řešení CSRF zranitelnosti. Nebo je tam nějaký problém na který zapomínám?
David Grudl #2
#1 ic, mám za to, že cookies byl použitelné o něco dřív, takže jsem origin už pak nezkoumal.
Elektricman #3
A čím je daný, že *.github.io je cross-site, ale *.nette.org je same-site?
David Grudl #4
#3 Elektricman, je na to seznam https://publicsuffix.org/list/
Jakub Vrána #5
Při chybě přesměrovat na homepage považuji za velmi nešťastné. Cookie může zmizet kvůli expiraci, kvůli tomu, že uživatel uvolňoval místo na mobilu nebo z několika dalších důvodů. Lepší je zůstat na stránce a ujistit se, že uživatel chtěl opravdu danou operaci provést. Řekl bych, že šance nějaké chyby je mnohem větší než šance útoku.
David Grudl #6
#5 Jakub Vrána, Zmizet nemůže. Je naprosto nereálné, že si uživatel během vyplňování formuláře v administraci nebo těsně před odkliknutím odkazu smazat položku vymaže cookie na webu. A pokud to udělá, klidně jeho požadavek zahodím výměnou za ochranu všech uživatelů před CSRF.
David Macek #7
#6 David Grudl, Pokud tokeny proti CSRF u vás nemají uměle omezenou platnost a vydrží pár dnů i paralelní práci ve více záložkách, pak bych byl ochoten možná souhlasit. Stává se mi ale, že mi legitimně vyplněný formulář neprojde, protože platnost jeho tokenu vypršela nebo byl token zneplatněn prací v druhé záložce. Pak by navržený přístup s explicitním zahozením dat byl ještě horší než současná loterie, kdy občas dostanu příležitost formulář znovu odeslat a občas jsem nucen ho navíc znovu vyplnit.
David Grudl #8
#6 David Grudl, tento článek popisuje systémové řešení na bázi SameSite cookie. S „nekonečnou“ platností (session cookie), jedna cookie pro celý web. Všechny vámi zmiňované problémy tady vůbec neexistují.
David Macek #9
#8 David Grudl, pravda. Děkuji za osvětlení.
Patrik #10
Zhruba od 28. 7. 2020 se v chromě zobrazují problémy s nevyplněnu SameSite cookie. Nešlo si nevšimnout, že se to prakticky ukazuje všude kde je například reCaptcha od Googlu nebo Youtube atd. jelikož to využívá iframe. Ještě to moc nechápu, takže se asi zeptám blbě, ale zkusím to. Tyhle problémy se v consoli zobrazují, protože jsou na mé stránce a já si to musím opravit, nebo se vyskytují na stránkách Googlu (on si to musí opravit) a ke mě přecházejí kvůli tomu iframu?
Varování se mi zobrazuje pouze u stránek, kde se tyto služby využívají a jejich cookie třetí strany. Koukal jsem, že stejný problém mají i jiné stránky, například https://www.vzhurudolu.cz/…t-proc-a-jak kde se youtube skytuji.
Musí tohle opravit Google, nebo uživatel?
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.