tom@home.htb:~$

Blog o HTB

24 January 2021

kid/jku a vzdálené načítání klíčů

Úvod a kontext

Článek o uniklém JWT signing secretu řeší situaci, kdy útočník získá klíč používaný pro podepisování a pak sám vytváří důvěryhodné tokeny. Téma kid a jku je jiné. Tady nemusí signing secret uniknout vůbec. Problém vzniká ve chvíli, kdy server přenechá klientovi rozhodnutí, odkud se má ověřovací klíč vzít.

To je zásadní rozdíl. Bezpečný návrh říká: aplikace má vlastní trust store a token jí maximálně pomůže vybrat správný klíč z už důvěryhodné sady. Nebezpečný návrh říká: token sám ukáže na URL, soubor nebo keyset, kterému pak server uvěří. V tu chvíli už neověřuje podpis server, ale útočník.

Co kid a jku mají dělat správně

V bezpečném návrhu slouží hlavička JWT jen jako nápověda:

Klient tedy může říct ověř mě klíčem s ID abc123, ale nesmí říct ověř mě klíčem, který si stáhneš z mé URL.

Právě tenhle rozdíl bývá v implementaci rozmazaný. Vývojář chce podporu pro rotaci klíčů, několik issuerů nebo pohodlnou správu JWKS. Jenže pokud se tahle flexibilita přenese do klientem ovlivnitelných hlaviček bez pevného trust boundary, vzniká z ní přímý bypass celé tokenové důvěry.

Kde se z flexibilního návrhu stává chyba

Rizikové jsou hlavně tyto vzory:

Všechny tyto varianty vypadají jinak, ale chyba je stejná: útočník už neovlivňuje jen payload tokenu. Ovlivňuje i to, čím se jeho podpis bude ověřovat.

Příklad z praxe: TheNotebook

TheNotebook je čistá ukázka tohoto patternu. Aplikace používala asymetrické podepisování a vypadalo to jako bezpečnější varianta než klasické HS256 se shared secretem. Problém ale nebyl v algoritmu. Problém byl v tom, že hlavička tokenu obsahovala kid, které ukazovalo na URL s klíčem:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "http://localhost:7070/privKey.key"
}

Server pak při ověřování slepě sáhl na adresu z tokenu, stáhl si odtud klíč a použil ho k validaci podpisu. Jakmile mohl útočník změnit kid na vlastní HTTP server, stačilo:

  1. vygenerovat si vlastní pár klíčů,
  2. vystavit veřejný klíč na útočníkem řízené URL,
  3. podepsat token privátním klíčem,
  4. změnit v payloadu třeba admin_cap na true.

Tím se ochrana podpisem nezlomila kryptograficky. Zlomila se architektonicky. Server neověřoval, zda token podepsal důvěryhodný issuer, ale zda ho dokáže ověřit klíčem, který mu sám token podstrčil.

Proč je to jiné než uniklý signing secret

Je užitečné ten rozdíl držet ostře, protože v praxi se tyto problémy často slévají pod obecné „JWT bypass“.

Uniklý signing secret

Na strojích jako Secret, Player nebo Cereal byl problém jiný:

To je kompromitace klíče.

kid/jku trust failure

Na TheNotebooku signing secret nebo privátní klíč serveru uniknout nemusel. Server sám přijal cizí zdroj ověřovacího klíče.

To je kompromitace trust modelu.

Oba scénáře mohou skončit stejným výsledkem, tedy podvrženým admin tokenem. Obrana je ale jiná:

Jak vypadá bezpečný a nebezpečný model

Bezpečný model

Nebezpečný model

Právě v tomto bodě vzniká chyba, kterou nejde opravit jen „silnějším algoritmem“. RS256 ani ES256 nepomůže, pokud si ověřovací klíč necháš vybrat útočníkem.

Na co se zaměřit při auditu

Při review nebo testu je dobré projít několik konkrétních otázek:

Odkud se ověřovací klíč bere

Co přesně znamená kid

Jak je svázaný issuer a keyset

Jak se chová cache a fallback

Jak silně aplikace věří claimům po validaci

I u kid/jku platí stejná druhá otázka jako u signing secretu: jak moc aplikace podle tokenu skutečně rozhoduje. Pokud payload nese roli, tenant nebo administrativní příznaky, dopad je okamžitý.

Obrana a hardening

1. kid jen jako index do lokálního trust store

To je nejdůležitější pravidlo. kid smí vybírat z klíčů, které aplikace už zná jako důvěryhodné. Nesmí sloužit jako URL, cesta ani obecný klíč k dynamickému fetchi.

2. jku a podobné odkazy jen na pevně definované zdroje

Pokud aplikace opravdu potřebuje vzdálený JWKS, musí být endpoint svázaný se známým issuerem a kontrolovaný serverovou konfigurací. Ne tokenem.

3. Oddělit key discovery od klientského vstupu

Klient může předložit token. Ale issuer, JWKS a očekávaný algoritmus musí určit server podle vlastní konfigurace.

4. Striktně validovat issuer a algoritmus

I dobře navržený keyset se rozpadne, pokud server benevolentně přijímá cizí issuer, algoritmus nebo strukturu tokenu a teprve potom zjišťuje, čím ho ověří.

5. Monitorovat nečekané odchozí lookupy při validaci tokenů

Aplikace, která při zpracování JWT náhle navazuje HTTP spojení podle klientského vstupu, už bezpečnostně selhala. Právě to je praktický detekční signál podobných chyb.

Shrnutí klíčových poznatků

Co si odnést do praxe

tags: jwt - web - auth - jwks - kid - jku