Jednorázový tajný odkaz je adresa URL, která svůj obsah zobrazí právě jednou a pak už natrvalo nic neukáže. Prohlížeč odesílatele zašifruje tajnou zprávu pomocí AES-GCM-256 a server uloží jen šifrový text. Klíč putuje v části odkazu za znakem # (za #k=), kterou prohlížeče nikdy neodesílají. První zobrazení u příjemce zprávu dešifruje a v téže operaci uloženou kopii zničí.
Takový odkaz si můžete vytvořit bez účtu v bezplatném generátoru tajných odkazů.
Samotný mechanismus je jednoduchý. Zajímavé jsou až okrajové případy, protože právě v nich nedbalé implementace potichu propouštějí data.
Základní průběh
- Odesílatel vloží tajnou zprávu. Prohlížeč vygeneruje náhodný klíč, zašifruje obsah pomocí AES-GCM-256 přes Web Crypto API a odešle jen šifrový text. Server ho uloží pod náhodnou, těžko uhodnutelnou koncovkou a vrátí URL. Prohlížeč k ní připojí klíč jako část
#k=, která nikdy neopustí zařízení odesílatele. - Odesílatel pošle URL libovolným kanálem: e-mailem, v chatu, v SMS.
- Příjemce URL otevře. Prostý požadavek
GETvrátí jen stav. Když příjemce klikne na „Zobrazit“, server v jednom nedělitelném kroku označí tajnou zprávu jako spotřebovanou a vrátí šifrový text. Prohlížeč příjemce ho dešifruje klíčem z části za znakem #. - Každá další návštěva dostane místo obsahu stránku se stavem „zničeno“.
Rozhodující je krok 3. Pokud čtení a změna stavu proběhnou jako dva samostatné dotazy (SELECT pro kontrolu a pak UPDATE pro označení), dva souběžní návštěvníci mohou kontrolou projít dřív, než kterýkoli z nich zapíše změnu. Obsah pak dostanou oba. To je klasická chyba TOCTOU (time-of-check to time-of-use), která z vaší záruky „právě jednou“ udělá „obvykle jednou“.
Proč musí zničení proběhnout ve stejném kroku jako čtení?
Protože jinak mohou dva souběžní návštěvníci projít kontrolou dřív, než kterýkoli z nich tajnou zprávu označí jako spotřebovanou. Řešením je sloučit kontrolu, označení a čtení do jedné databázové operace. V Postgresu to s UPDATE ... RETURNING vypadá takto:
UPDATE secrets
SET burned_at = now()
WHERE slug = $1
AND burned_at IS NULL
AND expires_at > now()
RETURNING payload;
Příkaz UPDATE zamkne řádek, takže souběžní volající se seřadí za sebe. První odpovídá podmínce WHERE, nastaví burned_at a přes RETURNING dostane obsah zpět. Každý další volající najde burned_at už nastavené, neodpovídá mu žádný řádek a dostane stav „zničeno“. Mezi „kontrolou“ a „spotřebováním“ není žádné okno, protože jde o jediný příkaz.
LinkPilot právě z tohoto důvodu zaznamenává zobrazení databázovou funkcí v jednom nedělitelném kroku, takže souběžné pokusy o zobrazení nemohou tajnou zprávu vyzradit dvakrát. Pokud nějaký nástroj vybíráte, je rozumné zeptat se přímo dodavatele: zničí se zpráva v tomtéž nedělitelném kroku jako čtení?
Proč zničené odkazy vracejí HTTP 410, a ne 404?
Protože 410 Gone říká, že zdroj existoval a je natrvalo nedostupný, a přesně to zničená tajná zpráva je. Po spotřebování má odkaz selhat jasně a natrvalo. LinkPilot při zničení udělá dvě věci:
- Uložený obsah se přepíše hodnotou
[REDACTED]v tomtéž zápisu, který mění stav. Ani pozdější průnik do databáze proto spotřebovanou tajnou zprávu neobnoví. - Další pokusy o zobrazení vrátí HTTP 410 Gone se srozumitelnou stránkou „už zobrazeno a zničeno“.
410 je správný stavový kód: znamená, že zdroj existoval a je záměrně a natrvalo nedostupný. Oproti tomu 404 říká „o ničem takovém nevím“. Rozdíl je důležitý i pro lidi. Příjemce, který uvidí „tato tajná zpráva už byla zobrazena“, ví, že to má říct odesílateli. Ten si pak projde auditní záznam, a pokud zprávu nezobrazil příjemce, přihlašovací údaje změní.
Těžko uhodnutelné koncovky a proč nestačí
Koncovka odkazu je to jediné, co stojí mezi veřejným internetem a nepřečteným šifrovým textem (klíč je v části za znakem #, ne na serveru), takže musí nést skutečnou entropii. Koncovka z kryptograficky bezpečného zdroje náhody, třeba 22 znaků v base62, nese zhruba 128 bitů. Postupné zkoušení všech možností tak není reálný útok při žádné rychlosti požadavků, kterou by server přežil.
Entropie koncovky ale chrání jen před uhodnutím. Nijak nepomůže, když URL někdo zahlédne: v historii prohlížeče, v logu proxy, v přeposlaném e-mailu, při sdílení obrazovky nebo ve správci schránky. Proto seriózní implementace přidávají další vrstvy:
- Vypršení platnosti, aby zahlédnutá URL podle plánu přestala fungovat.
- Heslovou frázi, aby samotná zahlédnutá URL nestačila.
- Zničení po přečtení, aby zahlédnutá URL po oprávněném přečtení neměla žádnou cenu.
Náhledy odkazů a boti
Chatovací aplikace (Slack, Teams, iMessage) a bezpečnostní skenery e-mailu vytvářejí z URL náhled tak, že ji hned po doručení zprávy načtou požadavkem GET. Pokud váš koncový bod pro zobrazení zničí zprávu už při GET, nástroj pro náhledy tajnou zprávu zničí dřív, než ji uvidí člověk. U naivních nástrojů typu „zničit po přečtení“ je to v praxi nejčastější selhání.
Obrana, jak ji implementuje LinkPilot:
- Oddělená akce pro metadata a pro zobrazení. Požadavek
GETna koncový bod pro zobrazení vrátí jen stav a informaci, zda je vyžadována heslová fráze. Zobrazení se zaznamená až při výslovném požadavkuPOST, tedy při akci, kterou člověk udělá kliknutím na tlačítko „Zobrazit“. - Ochrana heslovou frází (Pro). Když je heslová fráze nastavená, bez správného hashe nikdo nedostane žádný obsah, ať je to bot, nebo člověk.
Cache-Control: no-storeu každé odpovědi se zobrazením, aby obsah tajné zprávy neukládal do mezipaměti žádný prostředník.
Celé toto zpracování popisuje stránka o bezpečnostní architektuře LinkPilotu.
Životní cyklus stav po stavu
| Stav | Spouštěč | Co uvidí návštěvník | Lze obsah obnovit? |
|---|---|---|---|
| „Active“ (aktivní) | Vytvoření | Stránka pro zobrazení (s výzvou k heslové frázi, pokud je nastavená) | Ano, zbývá jedno zobrazení |
| „Burned“ (zničeno) | Dosažen limit počtu zobrazení | „Už zobrazeno a zničeno“, HTTP 410 | Ne, obsah je přepsán |
| „Expired“ (platnost vypršela) | Uplynul termín platnosti | Stránka se stavem „platnost vypršela“ | Ne, při dalším přístupu se přepíše |
| „Revoked“ (zneplatněno) | Odesílatel odkaz zneplatní v řídicím panelu | Odmítne zobrazit obsah, HTTP 410 | Ne, okamžitě a natrvalo |
Každý přechod se zapíše do časové osy auditu, kterou LinkPilot nabízí v tarifu Pro. Typy událostí jsou created, viewed, failed_passphrase, burned, expired a revoked, každá s časovým razítkem a hashovanou IP adresou (SHA-256 se solí, která se denně mění; samotnou IP adresu nikdy neukládáme). Díky tomuto záznamu se z „myslím, že to dostali“ stane „zobrazeno v 10:05 z očekávané země, jedno zobrazení, pak zničeno“.
Běžná rozšíření
- Ochrana heslovou frází: před zobrazením se vyžaduje heslová fráze předaná jiným kanálem, takže uniklá URL sama o sobě neznamená uniklou tajnou zprávu. V LinkPilotu je to funkce tarifu Pro (29 $/měs.) spolu s vlastní dobou platnosti a časovou osou auditu; generátor bez účtu ji nastavit neumí. Počet pokusů omezujte; LinkPilot povoluje 5 pokusů za minutu z jedné IP adresy na jednu tajnou zprávu a zaznamená každý neúspěch.
- Vypršení platnosti po určité době: tajná zpráva zanikne po nastavené době, i když ji nikdo nepřečte. Formulář LinkPilotu po přihlášení nabízí v každém tarifu 1 hodinu, 24 hodin nebo 7 dní a v tarifu Pro i vlastní datum a čas; generátor bez účtu nastavuje ve výchozím stavu 7 dní. Pokrývá případ, kdy odkaz zůstane zapomenutý v e-mailové schránce, a ten zničení po přečtení nevyřeší.
- Ruční zneplatnění: odesílatel může nepřečtenou tajnou zprávu zneplatnit v řídicím panelu, jakmile se mu něco nezdá. Levná pojistka pro situaci „odešlo to jinému Alexovi“.
Okrajové případy, na kterých se pozná dobrá implementace
Jako první ho otevře nesprávný člověk
Zničení po přečtení nesprávnému čtenáři nezabrání, ale chybné doručení zviditelní. Zamýšlený příjemce narazí na stránku se zničenou zprávou, auditní záznam ukáže jedno zobrazení, které jste nečekali, a vy přihlašovací údaje změníte. Je to nepříjemné, ale ohraničené: právě jedno vyzrazení, rychle odhalené. Srovnejte to s tajnou zprávou vloženou do chatu, kde byste se o tom nikdy nedozvěděli.
Problém se snímky obrazovky
Jednorázový znamená jedno zobrazení, ne jednu kopii. Příjemce si tajnou zprávu může vyfotit, zkopírovat nebo přepsat a odkaz tomu nijak nezabrání. Dodavatelé, kteří naznačují opak, prodávají smyšlenku. Poctivě řečeno: zničení po přečtení řídí kanál doručení, ne příjemce. Důvěra v příjemce je vstupní předpoklad, ne výsledek.
Přednačítání v prohlížeči a spekulativní načítání
Některé prohlížeče a aplikace předem načítají URL, na které uživatel pravděpodobně klikne. Dobře postavený nástroj zprávu zničí jen při výslovném POST, takže přednačítání se chová stejně jako nástroj pro náhledy: načte kostru stránky, nikdy ale obsah.
Kdy jednorázový odkaz nepoužívat
- Údaj budete potřebovat znovu. Možnost obnovení je hlavním cílem správce hesel a přesným opakem toho, oč jde jednorázovému odkazu. Použijte položku v trezoru.
- Potřebuje ho víc příjemců. Jeden odkaz, jedno přečtení. Rozeslání víc lidem znamená buď jeden odkaz pro každého (v pořádku a dohledatelné v auditu), nebo přiznat, že místo toho potřebujete sdílené řízení přístupu.
- Údaj spotřebovává stroj. Služby a CI mají číst ze správce tajných údajů, ne klikat na odkazy.
Pokud chcete mechanismus vidět v akci, vytvořte si odkaz v bezplatném generátoru tajných odkazů: bez účtu, zašifruje se ve vašem prohlížeči a po prvním zobrazení zmizí.
Související články
- Bezpečné sdílení hesel: srovnání 5 metod
- One time secret, jednorázový odkaz a tajný odkaz: krátké stránky s nástrojem nahoře
- Je OneTimeSecret bezpečný?
- Nejlepší alternativy k OneTimeSecret
- OneTimeSecret vs Privnote vs Password Pusher
- Odkazy s omezenou platností: tajná zpráva se zničí, ne jen přesměruje
- Bezplatný generátor tajných odkazů
Zdroje
- W3C, Web Cryptography API.
- IETF, RFC 3986, oddíl 3.5 Fragment.
- IETF, RFC 9110: HTTP Semantics, oddíl 15.5.11 410 Gone.
- PostgreSQL Documentation, UPDATE.
