JWT token jako session cookie

V nedávné době se objevilo několik projektů, kde zadáním byl test webové stránky, která na pozadí provolávala API endpointy a k autentizaci byly použity JWT tokeny. Ve výsledné zprávě bylo reportováno několik zranitelností, které vyplývají ze samotné podstaty návrhu tokenů a donutilo mě to k zamyšlení. Jsou naše výtky oprávněné? Jaké bezpečnostní výzvy plynou z použití JWT tokenů? K jakému využití jsou vůbec vhodné?

Co je JWT token?

Na úvod stručně shrňme, co to jsou JWT tokeny. Pro detailní vysvětlení doporučuji dohledat jiné zdroje, nicméně to nejdůležitější stručně najdete zde. Klasické session ID bývá náhodně generované a uložené na serveru, kde je svázané s uživatelskou relací. Naproti tomu JWT token se skládá ze tří částí, které mají obsah a význam: hlavičky, těla a podpisu. V těle tokenu jsou pak uloženy klíčové informace, jako například uživatelské jméno, díky kterým je identifikován uživatel a tokeny tak nemusí být uloženy v databázi. Podpis tokenu pak zajišťuje, že daty v těle nebylo manipulováno. Pří každém požadavku server ověří integritu tokenu a načte informace z jeho těla.

Příklad jednoduchého JWT tokenu a jeho dekódovaného obsahu.

Mohlo By Vás Zajímat

[]JSON Web Token for Java Cheat Sheet
[]ASVS Session Management
[]JWT issues
[]Old but funny diagram why not to use JWT
JWT token
JWT token decoded

Výhodou JWT tokenu tedy zjevně je, že není třeba držet session na serveru, je to tzv. stateless metoda. Je také možné jednoduše používat tokeny na více serverech, či aplikacích, stačí, aby byla sdílena tajemství používána k podpisu.

Pro podrobnější vysvětlení doporučuji například článek zde:https://jwt.io/introduction

Jaké problémy/výzvy z toho vyplývají?

Při použití JWT tokenů je třeba si dát pozor na pro ně typické zranitelnosti, jako například opravdu ověřovat podpis (ne tokeny pouze dekódovat) nebo nepřijímat algoritmus „null“ a prázdný podpis, to ale není to, čemu se se bude tento článek věnovat.

Při testování session managementu dle OWASPu je testována také kategorie „Testing for Logout Functionality“, tedy testování odhlášení uživatele (OWASP). Jako obvyklá chyba je zmíněn případ, kdy odhlášení uživatele je provedeno pouze smazáním/přepsáním cookie v prohlížeči, zatímco na straně serveru zůstává stále platná. Toto je ovšem velmi častý případ při použití JWT tokenu. Typicky nejsou nijak a nikdy ukládány na straně serveru a ten tedy nemá možnost je zneplatnit. Token je platný až do data expirace, které je uvedeno v těle, nehledě na to, zda se uživatel mezitím odhlásil, či ne.

Další výzvou je předání tokenu ze strany serveru i jeho samotné posílání v requestech. Objevují se řešení, kdy je JWT token posílám jako klasická cookie, nicméně častěji ho API na pozadí přijímá v hlavičce Authorization. V tomto případě ovšem token musí být přístupný pro čtení javascriptu, aby bylo možné tuto custom hlavičku ke každému requestu předat – což je bezpečnostní riziko, neboť tak může být přístupné taky útočníkovi v případě, že v aplikaci objeví XSS. Zároveň také vyvstává otázka, jak token předat ze strany serveru. Jako jednoduché (a pro vývojáře webovek známé) řešení se nabízí předat ho v cookie; ta ovšem nesmí mít nastaven parametr HTTPOnly, aby k ní měl kód na straně klienta přístup. Zde se pak dostáváme do přímého rozporu s OWASP metodologií (kategorie WSTG-SESS-02 Testing for Cookies Attributes).

Tyto nedostatky se vyskytovaly téměř ve všech případech, kdy jsem se s JWT tokeny na projektech setkala.

ASVS

Neboť OWASP testing guide je obecnější návod pro testování, rozhodla jsem se ověřit tyto problémy také pomocí Application Security Verification Standard od OWASPu. ASVS je seznam téměř tří set velmi konkrétních „checků“ rozdělených do kategorií a také do tří různých úrovní bezpečnosti. Splnění všech kontrol je vyžadováno pouze pro nejvyšší úroveň bezpečnosti kritických aplikací. Většina "běžných" aplikací spadá do druhé úrovně. Podívejme se tedy, jestli zde jsou konkrétní body, které jsouv rozporu s viděnou praxí implementace JWT jako session identifikátorů.

První bod je v kapitole V3.2 Session Binding a týká se ukládání tokenů, vidíme však, že HTML 5 session storage je považována za bezpečnou variantu.

ASVS 3.2

Problematičtější body nalezneme v kapitole Session Termination (ukončení session):

ASVS 3.3

Překlad jednotlivých bodů je:

3.3.1 Ověřte, že odhlášení a vypršení platnosti zneplatní token relace, takže tlačítko zpět nebo následná spoléhající se strana neobnoví ověřenou relaci, a to ani mezi spoléhajícími se stranami.

3.3.3 Ověřte, zda aplikace poskytuje možnost ukončit všechny ostatní aktivní relace po úspěšné změně hesla (včetně změny prostřednictvím obnovení hesla) a zda je tato možnost účinná pro celou aplikaci, federativní přihlášení (pokud existuje) a všechny spoléhající se strany.

3.3.4 Ověřte, zda uživatelé mohou zobrazit a (po opětovném zadání přihlašovacích údajů) odhlásit všechny aktuálně aktivní relace a zařízení.

Jak je vidět, všechny tyto body jsou problematické.

Oficiální doporučení

Problémy zmíněné výše vycházejí ze samotné podstaty JWT tokenů. Znamená to tedy, že by se neměly používat? Doporučení pro „správnou“ implementaci JWT tokenů jako session identifikátoru lze najít v JSON Web Token for Java Cheat Sheet od OWASPU. Obecná doporučení zde popsaná jsou použitelná nejen pro Javu.

Níže si popíšeme doporučované řešení na jednotlivé problémy. Řešení je převzato přímo z článku a doplněno o subjektivní vysvětlení, neboť ne vše je v originálním článku úplně jasné.

Token Sidejacking

Kapitola zabývající se ukradnutím tokenu útočníkem, který pak může být použit pro přístup k session. U cookies se za dostatečnou obranu proti sidejackingu považuje použití parametrů HTTPOnly a Secure, kdy cookie nemůže být odposlechnuta v nešifrované komunikaci (Secure), ani ukradena pomocí javascriptu (HTTPOnly).

V případě, kdy je JWT posíláno v hlavičce requestu a není možné jednoduše přidat výše zmíněné parametry pak OWASP navrhuje následující řešení: přidání „uživatelského kontextu“ do těla tokenu a pomocí tohoto kontextu svázání tokenu se zabezpečenou cookie. Přesný postup je následující

- Při autentizaci je vygenerován náhodný řetězec

- Řetězec je zaslán uživateli jako zabezpečená cookie (flags: HttpOnly + Secure + SameSite + Max-Age + cookie prefixes). Max-Age by měla být stejná, jako platnost tokenu.

- Do těla JWT tokenu je uložena SHA256 hash náhodného řetězce.

Při kontrole validity requestu musí kromě tokenu být na server zaslána také ona cookies a musí sedět hash cookie s hashí v těle tokenu. Pro zaslání validního požadavku na server je tedy potřeba zaslat také plně zabezpečenou cookie se správnou hodnotou a tedy je relace považována za bezpečnou. Neztrácíme ovšem stále tu výhodu, že žádná z hodnot nemusí být uložena na straně serveru.

No Built-In Token Revocation by the User

Zde narážíme na asi nejzjevnější problém a to je pevná platnost tokenu určená při vytvoření tokenu, tedy nijak nesouvisející s odhlášením uživatele.

První nabízené řešení, které není příliš intuitivní, je opět implementace „uživatelského kontextu“. Dle OWASPu může být v takovém případě logout „simulován“ pomocí smazání cookies s náhodným řetězcem. Je zde ten předpoklad, že během interakce s aplikací nemohl útočník cookie žádným způsobem získat, protože byla plně zabezpečena.

Intuitivnější řešení je implementace „denylistu“, databáze (tabulky), která bude obsahovat hashe nevalidních tokenů a konec jejich platnosti. Při každé kontrole tokenu je pak třeba zkontrolovat také to, zda se v této databázi nenachází.

Token storage on client side

Poslední problém je, jak uložit token na straně klienta. Za bezpečnou variantu se považuje návrh, který opět využívá „uživatelského kontextu“. Je třeba splnit následující body

  • držet token v sessionStorage prohlížeče, nebo použít JavaScipt closures s privátními proměnnými,
  • posílat token v jako Bearer v Authorization hlavičce,
  • přidat do tokenu „uživatelský kontext“ (popisovaný v řešení Token Sidejacking).

Uložení tokenu v sessionStorage nijak nebrání ukradnutí tokenu v případě XSS zranitelnosti a proto je vyžadován „uživatelský kontext“, díky kterému není možné token sám o sobě využít. Všimněme si však, že i tak je cílem minimalizovat riziko krádeže tokenu a proto je vyžadováno využití sessionStorage - dočasného úložiště, které je přístupné pouze dané záložce a po jejím zavření se smaže.

Jako varianta je zde zmíněna i možnost zasílat token přímo v cookie, v takovém případě je pouze třeba ošetřit CSRF a řešení je bezpečné. Nicméně málokdy se s takovým návrhem setkáváme, vzhledem k podstatě využití tokenů.

Závěr

Jak je vidět, správně a plně ošetřit bezpečnostní problémy JWT tokenů, pokud se je rozhodneme využít v klasické webové aplikaci pro správu relace, není úplně přímočaré. Co si z toho všeho odnést? Klíčová mi přijde věta z cheatsheetu pro Javu, v překladu „Pokud vaše aplikace nepotřebuje být zcela stateless, můžete zvážit použití tradičního systému relací, který poskytují všechny webové frameworky, a řídit se radami z cheat sheetu pro správu relací. Pro stateless aplikace je však při dobré implementaci vhodným kandidátem.“

Ovšem nežijeme v ideálním světě a stále musíme hledat hranici mezi jednoduchostí, rychlostí, uživatelskou přívětivostí na straně jedné a bezpečností na straně druhé. Důležité je, že pokud se pro použití JWT tokenů rozhodnete, budete znát a akceptovat rizika, která z toho vyplývají.

Mohlo By Vás Zajímat

[]JSON Web Token for Java Cheat Sheet
[]ASVS Session Management
[]JWT issues
[]Old but funny diagram why not to use JWT

Další Autorovy Články

[]SMB Relay