Kertakäyttöinen salainen linkki on URL-osoite, joka näyttää sisältönsä
täsmälleen kerran ja lakkaa sen jälkeen pysyvästi toimimasta. Lähettäjän
selain salaa salaisuuden AES-GCM-256-salauksella, palvelin tallentaa vain
salatekstin ja avain kulkee linkin #-merkin jälkeisessä osassa
(#k=), jota selaimet eivät koskaan lähetä. Kun vastaanottaja
avaa salaisuuden ensimmäisen kerran, sama toiminto purkaa salauksen ja
tuhoaa tallennetun kopion.
Sellaisen voi tehdä ilman tiliä ilmaisella salaisten linkkien luontityökalulla.
Mekanismi on yksinkertainen, mutta kiinnostavaksi sen tekevät erikoistapaukset. Juuri niissä huolimattomat toteutukset vuotavat huomaamatta.
Perusvaiheet
- Lähettäjä liittää salaisuuden. Selain luo satunnaisen avaimen, salaa
sisällön AES-GCM-256-salauksella
Web Crypto API:n avulla ja
lähettää vain salatekstin. Palvelin tallentaa sen satunnaisen,
vaikeasti arvattavan polkutunnuksen alle ja palauttaa URL-osoitteen.
Selain lisää avaimen osoitteen loppuun #-merkin jälkeiseen osaan
muodossa
#k=. Tämä osa ei koskaan poistu lähettäjän laitteelta. - Lähettäjä jakaa URL-osoitteen millä tahansa kanavalla: sähköpostilla, pikaviestimellä tai tekstiviestillä.
- Vastaanottaja avaa URL-osoitteen. Pelkkä
GET-pyyntö palauttaa vain tilan. Kun vastaanottaja napsauttaa näyttämispainiketta, palvelin merkitsee salaisuuden käytetyksi ja palauttaa salatekstin samassa toiminnossa, jota ei voi jakaa osiin. Vastaanottajan selain purkaa salauksen #-merkin jälkeisessä osassa olevalla avaimella. - Jokainen myöhempi käynti saa sisällön sijaan tilasivun, jonka mukaan salaisuus on tuhoutunut.
Ratkaiseva on vaihe 3. Jos lukeminen ja tilan päivitys tehdään kahtena
erillisenä kyselynä (ensin SELECT tarkistaa, sitten UPDATE merkitsee),
kaksi samanaikaista katsojaa voi kumpikin läpäistä tarkistuksen, ennen
kuin kumpikaan kirjoittaa. Molemmat saavat sisällön. Tämä on klassinen
TOCTOU-virhe (time-of-check to time-of-use), ja se muuttaa lupauksen
”täsmälleen kerran” muotoon ”yleensä kerran”.
Miksi lukeminen ja tuhoaminen on tehtävä yhtenä vaiheena?
Muuten kaksi samanaikaista katsojaa voi kumpikin läpäistä tarkistuksen,
ennen kuin kumpikaan merkitsee salaisuuden käytetyksi. Korjaus on
yhdistää tarkistus, merkintä ja lukeminen yhdeksi tietokantatoiminnoksi.
Postgresissa se näyttää tältä, kun käytetään lausetta
UPDATE ... RETURNING:
UPDATE secrets
SET burned_at = now()
WHERE slug = $1
AND burned_at IS NULL
AND expires_at > now()
RETURNING payload;
UPDATE-lauseen ottama rivitason lukitus pakottaa samanaikaiset kutsujat
jonoon: ensimmäinen täyttää WHERE-ehdon, asettaa sarakkeen burned_at
ja saa sisällön takaisin RETURNING-osan kautta. Jokainen myöhempi
kutsuja huomaa, että burned_at on jo asetettu, ei osu yhteenkään
riviin ja saa tuhoutuneen tilan. Tarkistuksen ja käyttämisen välissä ei ole
aukkoa, koska ne ovat sama lause.
LinkPilot kirjaa avaukset juuri tästä syystä tietokantafunktiolla, joka lukee ja merkitsee samalla hetkellä: samanaikaiset näyttämisyritykset eivät voi vuotaa salaisuutta kahdesti. Jos arvioit työkalua, tätä on aivan aiheellista kysyä suoraan toimittajalta: tapahtuvatko lukeminen ja tuhoaminen yhtenä vaiheena, jota ei voi jakaa osiin?
Miksi tuhoutuneet linkit palauttavat HTTP 410:n eivätkä 404:ää?
410 Gone kertoo, että resurssi on ollut olemassa ja on pysyvästi poissa, ja juuri sitä tuhoutunut salaisuus on. Käytetyn linkin pitää epäonnistua selvästi ja pysyvästi. LinkPilot tekee tuhoutumisen yhteydessä kaksi asiaa:
- Tallennettu sisältö korvataan merkkijonolla
[REDACTED]samassa kirjoituksessa, joka vaihtaa tilan. Siksi edes myöhempi tietokantamurto ei palauta käytettyä salaisuutta. - Myöhemmät näyttämisyritykset palauttavat vastauksen HTTP 410 Gone ja selkeän sivun, jonka mukaan sisältö on jo avattu ja tuhottu.
410 on oikea tilakoodi: se tarkoittaa, että resurssi on ollut olemassa ja on tarkoituksella ja pysyvästi poissa. 404 taas sanoo, ettei resurssista ole koskaan kuultukaan. Ero on tärkeä myös ihmisille. Vastaanottaja, joka näkee ilmoituksen ”tämä salaisuus on jo avattu”, tietää kertoa asiasta lähettäjälle. Lähettäjä voi tarkistaa tarkastuslokin ja vaihtaa tunnukset, jos avaaja ei ollut vastaanottaja.
Vaikeasti arvattavat polkutunnukset ja miksi ne eivät riitä
Polkutunnus on ainoa asia julkisen internetin ja vielä avaamattoman salatekstin välissä (avain on #-merkin jälkeisessä osassa, ei palvelimella), joten siinä on oltava todellista entropiaa. Esimerkiksi 22 merkin base62-polkutunnus, joka on poimittu kryptografisesti turvallisesta satunnaislähteestä, sisältää noin 128 bittiä. Läpikäynti ei ole realistinen hyökkäys millään pyyntötahdilla, jonka palvelin kestäisi.
Polkutunnuksen entropia suojaa kuitenkin vain arvaamiselta. Se ei auta mitään, jos URL-osoite nähdään: selainhistoria, välityspalvelinten lokit, edelleenlähetetyt sähköpostit, näytönjaot ja leikepöydän hallintaohjelmat. Siksi vakavasti otettavat toteutukset lisäävät kerroksia:
- Voimassaoloaika, jotta nähty URL-osoite lakkaa toimimasta aikataulun mukaan.
- Salalause, jotta pelkkä nähty URL-osoite ei riitä.
- Tuhoutuminen lukemisen jälkeen, jotta nähty URL-osoite on arvoton oikean lukemisen jälkeen.
Linkkien esikatselut ja botit
Pikaviestinsovellukset (Slack, Teams, iMessage) ja sähköpostin
tietoturvaskannerit avaavat URL-osoitteista esikatselun hakemalla ne
GET-pyynnöllä heti, kun viesti toimitetaan. Jos näyttämisen
päätepiste tuhoaa salaisuuden GET-pyynnöstä, esikatselutoiminto
tuhoaa sen ennen kuin ihminen ehtii nähdä sen. Tämä on yksinkertaisten ”tuhoutuu
lukemisen jälkeen” -työkalujen yleisin todellinen vika.
Suojaukset, sellaisina kuin LinkPilot ne toteuttaa:
- Metatiedot ja näyttäminen ovat erillisiä toimintoja. Näyttämisen
päätepisteeseen tehty
GETpalauttaa vain tilan ja tiedon siitä, tarvitaanko salalausetta. Avaus kirjataan vain erillisestäPOST-pyynnöstä, jonka ihminen tekee napsauttaessaan näyttämispainiketta. - Salalausesuojaus (Pro). Kun salalause on asetettu, sisältöä ei palauteta kenellekään, botille tai ihmiselle, ilman oikeaa tiivistettä.
Cache-Control: no-storejokaisessa näyttämisvastauksessa, jotta välityspalvelimet eivät koskaan tallenna salaisuuden sisältöä välimuistiin.
LinkPilotin tietoturva-arkkitehtuurisivu kuvaa tämän käsittelyn alusta loppuun.
Elinkaari tila tilalta
| Tila | Laukaisija | Mitä kävijä näkee | Voiko sisällön palauttaa? |
|---|---|---|---|
| Aktiivinen | Luonti | Näyttämissivu (salalausekysely, jos asetettu) | Kyllä: yksi avaus jäljellä |
| Tuhoutunut | Avauskertojen raja täyttyy | ”Jo avattu ja tuhottu”, HTTP 410 | Ei: sisältö korvattu |
| Vanhentunut | Voimassaoloaika päättyy | Vanhentuneen linkin tilasivu | Ei: sisältö korvataan seuraavalla käyttökerralla |
| Mitätöity | Lähettäjä mitätöi linkin hallintapaneelista | Sisältöä ei näytetä, HTTP 410 | Ei: välittömästi ja pysyvästi |
Jokainen tilasiirtymä kirjautuu tarkastuslokiin (loki linkin
tapahtumista), joka on LinkPilotissa Pro-ominaisuus. Tapahtumatyypit ovat created, viewed,
failed_passphrase, burned, expired ja revoked, ja jokaisessa on
aikaleima ja tiivistetty IP-osoite (SHA-256 päivittäin vaihtuvalla
suolalla; raakaa IP-osoitetta ei koskaan tallenneta). Tämän lokin
ansiosta ”luulen, että hän sai sen” muuttuu muotoon ”avattu klo 10:05
odotetusta maasta, yksi avaus, sitten tuhoutui”.
Yleiset laajennukset
- Salalausesuojaus. Ennen näyttämistä vaaditaan toista kanavaa pitkin lähetetty salalause, joten vuotanut URL-osoite ei yksin tarkoita vuotanutta salaisuutta. LinkPilotissa tämä on Pro-ominaisuus (29 $/kk) samoin kuin mukautettu voimassaoloaika ja tarkastusloki; ilman tiliä toimiva työkalu ei voi asettaa salalausetta. Rajoita yrityksiä: LinkPilot sallii 5 yritystä IP-osoitetta kohden minuutissa salaisuutta kohden ja kirjaa jokaisen epäonnistumisen.
- Aikaan perustuva voimassaoloaika. Salaisuus tuhoutuu asetetun ajan jälkeen, vaikka sitä ei olisi luettu. LinkPilotin lomake kirjautuneille tarjoaa vaihtoehdot 1 tunti, 24 tuntia tai 7 päivää kaikissa paketeissa, ja Pro-paketissa voi valita oman päivän ja kellonajan. Ilman tiliä toimiva työkalu asettaa oletuksena 7 päivää. Tämä kattaa tapauksen, jossa linkki unohtuu saapuneet-kansioon, mitä tuhoutuminen lukemisen jälkeen ei kata.
- Manuaalinen mitätöinti. Lähettäjä voi mitätöidä lukemattoman salaisuuden hallintapaneelista heti, kun jokin tuntuu olevan vialla. Halpa vakuutus tilanteeseen, jossa linkki lähti väärälle Alexille.
Erikoistapaukset, jotka erottavat hyvät toteutukset
Väärä henkilö avaa linkin ensin
Tuhoutuminen lukemisen jälkeen ei estä väärää katsojaa, mutta se tekee virheellisen toimituksen näkyväksi. Oikea vastaanottaja päätyy tuhoutuneen linkin sivulle, tarkastusloki näyttää yhden odottamattoman avauksen ja vaihdat tunnukset. Ikävää mutta rajattua: täsmälleen yksi altistus, joka huomataan nopeasti. Pikaviestiin liitetyn salaisuuden kohdalla et saisi sitä koskaan tietää.
Kuvakaappausongelma
Kertakäyttöinen tarkoittaa yhtä näyttämistä, ei yhtä kopiota. Vastaanottaja voi ottaa salaisuudesta kuvakaappauksen, kopioida sen tai kirjoittaa sen uudelleen, eikä linkki estä tätä mitenkään. Toimittajat, jotka antavat ymmärtää muuta, myyvät satua. Rehellinen tapa sanoa asia: tuhoutuminen lukemisen jälkeen hallitsee toimituskanavaa, ei vastaanottajaa. Luottamus vastaanottajaan on lähtöoletus, ei lopputulos.
Selaimen esilataus ja ennakoiva lataaminen
Jotkin selaimet ja sovellukset hakevat etukäteen URL-osoitteita, joita
todennäköisesti napsautetaan. Koska hyvin tehty työkalu tuhoaa
salaisuuden vain erillisestä POST-pyynnöstä, esilataus toimii täsmälleen
kuten esikatselutoiminto: se voi hakea sivun rungon, mutta ei koskaan sisältöä.
Milloin kertakäyttölinkkiä ei kannata käyttää
- Arvoa tarvitaan uudelleen. Palautettavuus on salasanojen hallintaohjelman koko suunnittelutavoite ja kertakäyttölinkin vastakohta. Käytä holvin kohdetta.
- Useat vastaanottajat tarvitsevat sitä. Yksi linkki, yksi lukeminen. Jos vastaanottajia on monta, joko luot jokaiselle oman linkin (toimii, ja jäljitettävyys säilyy) tai myönnät, että tarvitset sen sijaan jaetun käyttöoikeuksien hallinnan.
- Käyttäjä on kone. Palveluiden ja CI-järjestelmien pitää lukea salaisuudet salaisuuksien hallintapalvelusta, ei napsauttaa linkkejä.
Jos haluat nähdä mekanismin toiminnassa, luo linkki ilmaisella salaisten linkkien luontityökalulla. Tiliä ei tarvita, sisältö salataan selaimessasi ja se katoaa ensimmäisen avauksen jälkeen.
Lue lisää
- Salasanojen turvallinen jakaminen: 5 tapaa vertailussa
- One-time secret, kertakäyttölinkki ja salainen linkki: lyhyet sivut, joiden yläosassa on työkalu
- Onko OneTimeSecret turvallinen?
- Parhaat OneTimeSecret-vaihtoehdot
- OneTimeSecret vs Privnote vs Password Pusher
- Vanhenevat linkit: salaisuus tuhotaan, sitä ei vain ohjata muualle
- Ilmainen salaisten linkkien luontityökalu
Lähteet
- W3C, Web Cryptography API.
- IETF, RFC 3986, section 3.5 Fragment.
- IETF, RFC 9110: HTTP Semantics, section 15.5.11 410 Gone.
- PostgreSQL Documentation, UPDATE.
