Podpora SameSite cookie v Nette a CSRF

David Grudl
Nette Core | 8215
+
+28
-

Doplnil jsem do Nette (konkrétně do nette/http) podporu pro SameSite cookie, což je první systémová obrana proti CSRF. CSRF útok spočívá v tom, že útočník vyláká uživatele webu A na svou stránku na webu B, která nepozorovaně provádí v prohlížeči uživatele a tedy pod jeho identitou nějakou skrytou nepříjemnou činnost na webu A.

Popis najdete třeba zde:

SameSite cookie mají za cíl nahradit $form->addProtection() nebo Nextras Secured Links a nevyžadují nastartování session.

Ačkoliv se podpora pro SameSite cookie objevila v prohlížečích teprve nedávno, podle Caniuse ji už najdete v 68 % zařízení, což ještě stoupne za měsíc po vydání iOS 12.

(Zároveň je potřeba dodat, že značné procento zařízení je nepodporuje, takže addProtection & spol. zatím opustit nelze.)

Jak vypadá podpora v Nette 2.4 (2018–09–18)?

Tak jednak přibyl nový parametr $sameSite v metodě Nette\Http\Response::setCookie(), který může nabývat hodnot 'Lax', 'Strict' nebo null. Ale to není tak zajímavé.

Daleko užitečnější ovšem je posílání session ID s nastavením sameSite: Lax. Lax říká, že pokud se na web odešle POST formulář z jiné domény, nestane se tak pod aktuálně přihlášeným uživatelem. Jednoduše v HTTP požadavku bude chybět cookie se session ID (týká se to i AJAXových požadavků, iframe apod). Tohle se v praxi ukázalo jako problematické kvůli nestandardní implementaci v Safari pro iOS. (info)

Ochranu spustíte konfigurací:

session:
	cookieSamesite: Lax

Pojďme k dál.

Změny na webu by správně měly dělat jen POST požadavky, ale zcela běžně se k tomu používají i GET požadavky, tedy odkazy, jako například <a n:href="...">smazat</a>. U těchto odkazů také nechceme, aby je bylo možné vyvolat útočníkem z jiné domény. Řešením je buď odkazy zaměnit za tlačítka, u kterých ochranu zajišťuje už výše uvedená ochrana cookie se session ID, nebo zůstat u odkazů a kontrolovat na straně presenteru, zda akce byly vyvolány ze stejné domény.

Ukázka nahrazení odkazu za POSTové tlačítko:

odkaz
<a n:href="...">smazat</a>

nahradíme za tlačítko (pozn. attribut form nefunguje v IE 11)
<button formaction="..." form=postform>smazat</button>

+ na začátek stránky přidáme
<form id=postform method=post></form>

Pokud nepoužijeme tlačítko a zůstaneme u odkazů, je třeba kontrolovat na straně presenteru, zda akci vyvolal náš server.

Nejprve tedy aktivujeme podporu pro tuto feature pomocí konfigurace:

http:
	sameSiteProtection: yes

# zároveň aktivuje i výše uvedené cookieSamesite: Lax, které už není potřeba nastavovat

A pro test na straně presenteru poslouží metoda Nette\Http\Request::isSameSite(). Pokud odkaz nebyl odkliknut na našem serveru, musíme uživatele přesměrovat jinam, protože opakovaný požadavek vyvolaný stisknutím tlačítka refresh by už nemohl být tímto způsobem chráněn.

class MyPresenter extends Presenter
{
    public function handleDelete(int $id)
    {
		if (!$this->getHttpRequest()->isSameSite()) {
			$this->redirect('Sign:out');
		}
    }
}

Pokud na web přijdeme s prohlížečem, který SameSite cookie nepodporuje, vše bude fungovat, jen prostě odkazy nebudou chráněné. Proto není třeba váhat s nasazením této doplňující ochrany.

Doplněno:

Jak vypadá podpora v Nette 3?

V Nette 3 je ochrana signálů automaticky aktivní a nebude nutné ji aktivovat v konfiguraci. Netýká se to však session cookie, která příznak samesite kvůli odlišnostem v implementaci prohlížečů nemá.

Kontrolu jde pro konkrétní signál vypnout uvedením anotace:

class MyPresenter extends Presenter
{
    /**
     * @crossOrigin
     */
    public function handleDelete(int $id)
    {
    }
}
suwer
Člen | 33
+
-3
-

Mne se to nelibi. Ne kvuli myslence, ale kvuli tomu, ze je to celkem slozite na pochopeni, pro pouziti to vyzaduje hodne „dalsich informaci“ a nakonec, neni to univerzalni.

David Grudl
Nette Core | 8215
+
+20
-

Bezpečnostní věci jsou vždycky složité na pochopení, s tím se nedá moc dělat.

Lukes
Silver Partner | 68
+
+5
-

David Grudl napsal(a):
Pokud nepoužijeme tlačítko a zůstaneme u odkazů, musíme kontrolovat na straně presenteru, zda je vyvolal náš server. To by se v Nette 3 zřejmě zjednodušilo na uvedení anotace:

class MyPresenter extends Presenter
{
    /**
     * @secured (nebo @samesite ?)
     */
    public function handleDelete(int $id)
    {
    }
}

Nebylo by lepší se k tomu chovat jako k escapování, což znamená, že pokud nic neuvedu, tak je to ve výchozím nastavení nastavené a anotací to deaktivovat? Je tu nějaký důvod(bezpečnostní, výkonový…) proč by to tak nemělo být? Je to sice BC break, ale to by u Nette 3 nemuselo vadit. Přece jen víš jestli je použitý GET nebo POST.

Editoval Lukes (4. 9. 2018 10:32)

David Grudl
Nette Core | 8215
+
+6
-

Myslíš v případě signálů, tedy metod handleXyz()? To není špatný nápad, asi to zkusím nasadit a uvidíme, jak se to osvědčí.

Lukes
Silver Partner | 68
+
0
-

Přesně tak. Ono většinou actionXyz() se nepoužívá na nějakou přímou akci, která něco mění v DB. Jediný případ, který mě napadá je nějaká API, ale zase do té se neautentizuje přes session id v cookies…

David Grudl
Nette Core | 8215
+
0
-

Ještě je otázka, jak se v případě neoprávněného přístupu zachovat. Asi bude nutné někam přesměrovat, zobrazit jen chybovou stránku nestačí, protože uživatel by mohl stisknout F5 a tím požadavek zopakovat, ale tentokrát už by byl vyvolaný ze same site.

Lukes
Silver Partner | 68
+
0
-

Tak proč na tohle nevyužít ErrorPresenter. Samozřejmě by se muselo redirectovat místo forwardu. Myslím, že nic univerzálnějšího není. Ještě by šlo přesměrovat na action, na které byl signál volán. Nebo homepage?

Editoval Lukes (4. 9. 2018 12:52)

ali
Člen | 342
+
0
-

David Grudl napsal(a):

Ještě je otázka, jak se v případě neoprávněného přístupu zachovat. Asi bude nutné někam přesměrovat, zobrazit jen chybovou stránku nestačí, protože uživatel by mohl stisknout F5 a tím požadavek zopakovat, ale tentokrát už by byl vyvolaný ze same site.

Co takhle nastavit destinaci v configu?

Pripade pokud by si nekdo nejaky konkretni handle chtel prizpusobit, tak by se to mohlo nastavit pres anotaci.

Editoval ali (4. 9. 2018 16:50)

Lumeriol
Generous Backer | 62
+
+2
-

Napadá někoho, jak vyřešit problém typu:

Eshop – objednávka – přesměrování na platební bránu na jiný web (přes presenter->redirect) – návrat z platební brány na původní stránku bez zrušení sessions?

Respektive je možné vypnout tuto kontrolu pouze pro část webu, či spíše mít z některých domén povolený přístup?

ic
Člen | 430
+
+1
-

Asi dobré to nasadit co možná nejdříve… podle posledního oznámení Googlu ( https://thehackernews.com/…cookies.html ) totiž bude v Chromu i funkce pro odstranění sledovacích cookies. A to která cookie je a není „sledovací“ se bude rozhodovat právě podle nastavení SameSite . A cookies postaru bez jakéhokoliv nastavení se budou interpretovat jako cookie třetí strany a pokud bude uživatel paranoidní a často je mazat bude se z takového webu i často odhlašovat. Mít to nastavené už dopředu (Google to zatím jen ohlásil jako plán, nic konkrétního zatím v Chromu není) může zajistit, že se nepromažou i dlouhodobější cookies, které nastavuji už teď i když by ta funkce měla přijít až za X měsíců.

ViPEr*CZ*
Člen | 817
+
0
-

Na 2.4 mi to PHPSESSID posílá bez secured a bez nastaveného sameSite. Ostatní cookies to mají. To je OK (prohlížeč píše, že se mu to nebude do budoucna líbit)? Nebo něco dělám špatně já?

Editoval ViPEr*CZ* (6. 7. 2020 19:02)

David Grudl
Nette Core | 8215
+
+2
-

Chrome říká, že future release of Chrome will only deliver cookies with cross-site requests if they are set with SameSite=None and Secure.

Pokud máš cookie, u které chceš, aby byla přenášena cross-site, tak při vytvoření nastav SameSite na None.

Secure jde zapnout globálně https://doc.nette.org/cs/configuring#…

ViPEr*CZ*
Člen | 817
+
0
-

Moc děkuji. Měl jsem špatně konfigurák (někde jsem to zkopčil a měl jsem tam u sameSite null namísto none).
Pro tohle cookieSecure jsem měl ještě špatně závislosti v composeru. Už mi to funguje správně. Moooc ještě jednou díky.

TomHegr
Člen | 1
+
0
-

Je možnost nastavit si z pravidla výjimku např. pro localhost? Autentizaci dělám pro locahost na vzdáleném serveru, ale ta po nastavení SameSite na Lax již neprojde. Strejda Google je zatím bezradný.

xlastovi
Člen | 3
+
0
-

Já bych se rád zeptal ohledně 3.0. V minulosti jsem v rozsáhlé aplikaci (DMS) používal na akce (např. mazání) metody actionSomething. Znamená to, že ty nejsou chráněny a chráněny jsou pouze signály?