Kertakäyttölinkki, joka tuhoutuu pelkästä GET-pyynnöstä, kuolee heti, kun Slack, Teams tai sähköpostiyhdyskäytävä hakee sen esikatselua varten. Korjaus on palauttaa GET-pyyntöön vain metatiedot ja tuhota salaisuus vasta erillisellä POST-pyynnöllä, kun vastaanottaja valitsee näyttämisen. Varmistuksena toimii salalause, joka lähetetään toista kanavaa pitkin. Tämä kirjoitus kertoo, mitkä järjestelmät hakevat linkkejä kysymättä ja miten suunnittelu kestää ne kaikki.
LinkPilotin kertakäyttöisen salaisen linkin luontityökalu on rakennettu näin: esikatseluhaku saa vain sivun rungon. Salaisuus haetaan ja avaus lasketaan vasta, kun selain suorittaa sivun.
Tavallinen virhetilanne
Useimmat chat- ja sähköpostiohjelmat yrittävät näyttää esikatselun, kun ne huomaavat URL-osoitteen. Kulku on tällainen:
- Käyttäjä liittää salaisen linkin Slackiin.
- Slackin esikatselutoiminto (unfurler) hakee URL-osoitteen (lähettää
GET-pyynnön; Slack dokumentoi toiminnan kohdassa link unfurling). - Näyttämisen käsittelijäsi pitää pyyntöä ihmisen avauksena, tuhoaa salaisuuden ja palauttaa sisällön.
- Slack saa tuhotun sisällön (tai yleisen sivun) ja näyttää sen.
- Ihminen napsauttaa linkkiä, näkee ilmoituksen ”already viewed” ja pyytää lähettäjältä uuden.
Esikatselutoiminnolla ei ollut pahoja aikeita. Käsittelijäsi sekoitti tapahtuman ”joku haki URL-osoitteen” tapahtumaan ”vastaanottaja luki salaisuuden”. Ne ovat eri tapahtumia, ja koko suunnitteluongelma on pitää ne erillään.
Vaiheessa 3 piilee vielä pahempi versio: jos esikatselutoiminnon GET-pyyntö sai varsinaisen sisällön, salaisuus on nyt kolmannen osapuolen esikatselujärjestelmässä. Parhaassa tapauksessa se hävitettiin, kun esikatselu oli valmis. Sitä et kuitenkaan voi mitenkään varmistaa, eikä mikään lokitapahtuma kerro, että näin kävi.
Kuka hakee linkin ennen kuin vastaanottaja avaa sen?
Chat-palvelujen esikatselutoiminnot, sähköpostin tietoturvayhdyskäytävät, päätelaitteiden suojausohjelmat, yritysten välityspalvelimet ja esikatselulaajennukset, yleensä useampi kuin yksi niistä. ”Slack näyttää esikatseluja” vähättelee ongelmaa. Nykyaikaisella työpaikalla liitetyn URL-osoitteen hakee tyypillisesti usea järjestelmä ennen kuin yksikään ihminen napsauttaa sitä:
| Hakija | Milloin se hakee | Tunnistettavissa? |
|---|---|---|
| Chat-palvelujen esikatselutoiminnot (Slack, Teams, iMessage, WhatsApp, LinkedIn) | Heti, kun viesti lähetetään | Yleensä: omat user agent -tunnisteet, kuten Slackbot-LinkExpanding |
| Sähköpostin tietoturvayhdyskäytävät (Mimecast, Proofpoint, Microsoft Defender) | Toimitettaessa ja usein uudelleen napsautushetkellä | Usein ei: monet esiintyvät oikean selaimen user agent -tunnisteella |
| Päätelaitteiden suojaus ja virustorjunta | Kun URL-osoite saapuu vastaanottajan koneelle | Harvoin |
| Yritysten välityspalvelimet | Napsautushetkellä, ennen kuin käyttäjän pyyntö välitetään eteenpäin | Harvoin |
| Linkkien esikatselun selainlaajennukset | Kun osoitin viedään linkin päälle tai sivu latautuu | Ei: ne käyttävät käyttäjän omaa selainistuntoa |
Taulukon tiedot palveluntarjoajien toiminnasta muuttuvat jatkuvasti, joten lue yksittäisiä yhdyskäytäviä koskevat tiedot näin: ”julkaistu heinäkuussa 2026, tarkista ajantasaisista ohjeista”. Yhteinen piirre ei kuitenkaan muutu: jokainen näistä järjestelmistä lähettää pelkän GET-pyynnön, koska se on ainoa turvallinen ja yleispätevä tapa katsoa URL-osoitetta, jota ei itse hallitse.
Miten esikatselu estetään tuhoamasta kertakäyttölinkkiä?
Palauta GET-pyyntöön metatiedot ja tuhoa salaisuus vain erillisellä POST-pyynnöllä. HTTP:n semantiikka tukee tätä valmiiksi: RFC 9110, kohta 9.2.1 määrittelee GET-metodin turvalliseksi ja idempotentiksi, eli kuka tahansa saa lähettää sen vapaasti, koska sen ei kuulu muuttaa mitään. POST on toimintoa varten. Toteuta siis näyttäminen POST-toimintona näkyvän painikkeen takana:
GET /s/abc123 → HTML page: "Click to reveal this secret"
POST /s/abc123 → burns the secret, returns the payload
Nyt Slackin GET-pyyntö saa neutraalin metatietosivun. Salaisuus tuhoutuu vasta, kun ihminen napsauttaa painiketta. HTTP-semantiikkaa noudattava skanneri ei koske sisältöön lainkaan. Lähes kaikki noudattavat sitä, koska sivuvaikutuksia aiheuttavien POST-pyyntöjen lähettäminen mielivaltaisiin URL-osoitteisiin rikkoisi puolet verkosta.
Esimerkki vaihe vaiheelta
Seurataan yhtä linkkiä alusta loppuun. Luot salaisuuden, jossa on asetus ”Tuhoa lukemisen jälkeen” ja salalause, ja liität URL-osoitteen Slackin yksityisviestiin ja sähköpostiin.
- Slackin esikatselutoiminto lähettää pyynnön
GET /s/abc123. Vastaus: pieni metatietosivu, jossa tila on ”active” (käytössä) ja salalause vaaditaan. Avausta ei kirjata. Salaisuus on tallessa. - Vastaanottajan sähköpostiyhdyskäytävä kirjoittaa sähköpostissa olevan URL-osoitteen uudelleen ja hakee sen toimitushetkellä mainetarkistusta varten. Sama
GET, samat metatiedot. Salaisuus on yhä tallessa. - Vastaanottaja napsauttaa sähköpostissa olevaa linkkiä. Yhdyskäytävän napsautushetken skanneri hakee osoitteen vielä kerran (
GET) ja ohjaa sitten selaimen eteenpäin. - Selain lataa näyttämissivun, jälleen
GET-pyynnöllä. Sivulla on näyttämispainike ja salalausekenttä. Avausta ei vieläkään kirjata. - Vastaanottaja kirjoittaa salalauseen ja napsauttaa näyttämispainiketta. Selain lähettää pyynnön
POST /s/abc123. Avaus kirjataan, sisältö palautetaan ja salaisuus tuhoutuu. - Jokainen, joka hakee URL-osoitteen tämän jälkeen, saa sisällön sijaan selvän vastauksen ”already viewed and destroyed”.
Viisi hakua, yksi näyttäminen. Suhde on yritysympäristössä tavallinen. Ratkaisu, joka ei kestä sitä, pettää ensimmäisenä päivänä, ja lähettäjä liittää salasanan sen sijaan suoraan chattiin.
Puolustus kerroksittain
GET- ja POST-pyyntöjen erottelu
Hoitaa jokaisen sääntöjä noudattavan esikatselutoiminnon ja skannerin, eli valtaosan automaattisesta liikenteestä. Tämä on perusta, ja kaikki alla oleva on lisäsuojaa.
Salalause
Kattaa sen, mitä erottelu ei kata: JavaScriptiä suorittavat ja sivun kanssa toimivat hiekkalaatikot sekä ihmisten vahingossa tekemät napsautukset ja edelleen lähetetyt sähköpostit. Haittaohjelmia laukaiseva hiekkalaatikko voi napsauttaa näyttämispainiketta, mutta se ei voi antaa salalausetta, joka on kulkenut toista kanavaa pitkin. Lähetä linkki sähköpostilla ja salalause Slackin yksityisviestillä tai tekstiviestillä.
Bottien suodatus user agent -tunnisteen perusteella
Pudota tunnetut esikatselutoiminnot (Slackbot, LinkedInBot, Twitterbot) pois ennen kuin ne pääsevät näyttämislogiikkaan. Se kannattaa tehdä lisävarmistuksena, mutta yksinään se ei koskaan riitä: tietoturvaskannerit esiintyvät rutiininomaisesti oikean selaimen user agent -tunnisteella juuri siksi, etteivät haitalliset sivut voi piiloutua analyysiltä.
Lyhyt voimassaoloaika
Rajaa aikaa, jona vahinkoa voi syntyä. Kun linkki on voimassa vain viisitoista minuuttia, skannereille, lokitiedostoihin ja chat-historiaan jää URL-osoite, joka ei enää johda mihinkään.
Cache-Control: no-store
Aseta se jokaiseen näyttämisvastaukseen, jotta mikään välissä oleva järjestelmä, kuten yrityksen välityspalvelin, CDN tai selaimen välimuisti, ei säilytä kopiota sisällöstä sen yhden oikean lukukerran jälkeen.
Erityisesti sähköpostin tietoturvayhdyskäytävät
Yritysten yhdyskäytävät ansaitsevat oman osionsa, koska ne hakevat linkin kahdesti. Tämän luokan tuotteet (Microsoft Defender Safe Links on dokumentoitu esimerkki) kirjoittavat saapuvan postin jokaisen URL-osoitteen uudelleen niin, että se kulkee niiden oman skannausverkkotunnuksen kautta. URL-osoite haetaan tyypillisesti kerran toimitushetkellä mainetarkistusta varten ja uudelleen napsautushetkellä, ennen kuin ihminen ohjataan varsinaiseen kohteeseen.
Jos kumpi tahansa haku tuhoaisi salaisuuden, vastaanottaja näkisi aina kuolleen linkin. Käyttäjille vika näyttää satunnaiselta, mutta se on täysin ennustettava. GET/POST-erottelu kestää molemmat haut ilman lisätyötä, koska molemmat ovat pelkkiä GET-pyyntöjä. Napsautushetken uudelleenohjaus vie ihmisen sitten näyttämissivulle, jossa POST tapahtuu.
Reunatapaukset, jotka yhä aiheuttavat ongelmia
- JavaScriptiä suorittavat hiekkalaatikot. Jotkin haittaohjelmien laukaisuympäristöt piirtävät sivun ja toimivat sen kanssa. Aggressiivinen hiekkalaatikko voi periaatteessa napsauttaa painiketta, jonka takana on
POSTilman salalausetta. Salalause on varmistus tätä vastaan. - Linkki ja salalause samassa kanavassa. Jos molemmat kulkevat samassa sähköpostissa, kaikki yllä olevat suojaukset kutistuvat toiveeksi, ettei kukaan lue sitä sähköpostia. Erota kanavat aina.
- Edelleenlähetys. Jokainen edelleenlähetys uuteen chat- tai sähköpostijärjestelmään käynnistää uuden kierroksen esikatseluhakuja. Erottelun ansiosta se on vaaratonta, mutta jokainen välivaihe on uusi joukko lokeja, joissa URL-osoite on tallessa. Sekin puoltaa lyhyttä voimassaoloaikaa.
- Samanaikaiset näyttämiset. Jos kaksi ihmistä napsauttaa näyttämispainiketta yhtä aikaa, lukemisen ja tuhoamisen on tapahduttava yhtenä vaiheena, jota ei voi jakaa osiin: tasan yksi pyyntö voittaa, ja toinen saa vastauksen ”already viewed”. Toteutus, jossa lukeminen ja tuhoaminen ovat erillisiä vaiheita, voi palauttaa sisällön kahdesti.
Testaa oma työkalusi
Ennen kuin annat oikeita kirjautumistietoja minkään salaisuuksien jakopalvelun haltuun, tee tämä kahden minuutin tarkistus:
- Luo kertakäyttöinen testisalaisuus, jossa on asetus ”Tuhoa lukemisen jälkeen” päällä.
- Hae se päätteestä. Juuri näin esikatselutoiminto toimii:
curl -s -o /dev/null -w "%{http_code}" "<url>" - Avaa linkki selaimessa. Jos näet ilmoituksen ”already viewed”, työkalu tuhoaa salaisuuden
GET-pyynnöstä, eikä linkkiä ole turvallista liittää chattiin tai sähköpostiin. - Jos salaisuus säilyi, näytä se kunnolla selaimessa ja aja
curluudelleen. Nyt pitäisi tulla selvä vastaus siitä, että salaisuus on poissa tai tuhottu, eikä itse sisältö.
Näin LinkPilot toteuttaa tämän
Tiedoksi: LinkPilotin Secret Links -toiminto käyttää juuri tätä mallia. GET-pyyntö näyttämisen päätepisteeseen palauttaa vain metatiedot (tilan ja tiedon siitä, vaaditaanko salalause). Avaus kirjataan vain erillisellä POST-pyynnöllä. Salalauseet tiivistetään SHA-256:lla jo selaimessa, ja yrityksiä rajoitetaan viiteen minuutissa IP-osoitetta ja salaisuutta kohden. Jokaisessa näyttämisvastauksessa on Cache-Control: no-store, tuhoutuneet ja vanhentuneet salaisuudet palauttavat HTTP-tilakoodin 410 ja tallennettu sisältö on ylikirjoitettu, ja tarkastusloki tallentaa jokaisen tapahtuman tiivistetyllä IP-osoitteella, jonka suola vaihtuu päivittäin. Alkuperäistä IP-osoitetta ei tallenneta koskaan. Kaikki yksityiskohdat ovat tietoturva-arkkitehtuuria käsittelevällä sivulla.
Kokeile ilmaiseksi, ei rekisteröitymistä
Itsetuhoutuva viesti
Lähetä muistiinpano, joka katoaa, kun se on luettu kerran.
Tämän mallin rajat
Rehellisesti sanottuna: mikään yllä olevista ei suojaa salaisuutta sen jälkeen, kun se on näytetty oikealle vastaanottajalle. Vastaanottaja voi kopioida sisällön, ottaa siitä kuvakaappauksen tai lähettää sen eteenpäin. Malli ei auta, jos salalause kulkee linkin mukana. Se ei myöskään estä ihmistä, jolle linkki lähetettiin vahingossa, avaamasta sitä ennen oikeaa vastaanottajaa. Sitä varten on tarkastusloki: näet odottamattoman avauksen ja vaihdat kirjautumistiedot.
Lue myös
- Näin kertakäyttöiset salaiset linkit toimivat
- Miksi URL-osoitteet vuotavat arkaluonteisia tietoja
- LinkPilotin tietoturva-arkkitehtuuri
Lähteet
- IETF, RFC 9110: HTTP Semantics, section 9.2.1 Safe Methods.
- Slack API, Unfurling links in messages.
- Microsoft Learn, Safe Links in Microsoft Defender for Office 365.
