Tietoturva

    Linkkiskannerit ja kertakäyttöiset salaisuudet

    GET-pyynnöstä tuhoutuva kertakäyttölinkki kuolee Slackin tai sähköpostiyhdyskäytävän esikatseluun. Palauta GET:lle metatiedot, tuhoa vasta näytettäessä.

    W. Miller · Päivitetty · Tietoa LinkPilotista

    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:

    1. Käyttäjä liittää salaisen linkin Slackiin.
    2. Slackin esikatselutoiminto (unfurler) hakee URL-osoitteen (lähettää GET-pyynnön; Slack dokumentoi toiminnan kohdassa link unfurling).
    3. Näyttämisen käsittelijäsi pitää pyyntöä ihmisen avauksena, tuhoaa salaisuuden ja palauttaa sisällön.
    4. Slack saa tuhotun sisällön (tai yleisen sivun) ja näyttää sen.
    5. 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.

    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ä:

    HakijaMilloin se hakeeTunnistettavissa?
    Chat-palvelujen esikatselutoiminnot (Slack, Teams, iMessage, WhatsApp, LinkedIn)Heti, kun viesti lähetetäänYleensä: 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 virustorjuntaKun URL-osoite saapuu vastaanottajan koneelleHarvoin
    Yritysten välityspalvelimetNapsautushetkellä, ennen kuin käyttäjän pyyntö välitetään eteenpäinHarvoin
    Linkkien esikatselun selainlaajennuksetKun osoitin viedään linkin päälle tai sivu latautuuEi: 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.

    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.

    1. 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.
    2. 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.
    3. Vastaanottaja napsauttaa sähköpostissa olevaa linkkiä. Yhdyskäytävän napsautushetken skanneri hakee osoitteen vielä kerran (GET) ja ohjaa sitten selaimen eteenpäin.
    4. Selain lataa näyttämissivun, jälleen GET-pyynnöllä. Sivulla on näyttämispainike ja salalausekenttä. Avausta ei vieläkään kirjata.
    5. Vastaanottaja kirjoittaa salalauseen ja napsauttaa näyttämispainiketta. Selain lähettää pyynnön POST /s/abc123. Avaus kirjataan, sisältö palautetaan ja salaisuus tuhoutuu.
    6. 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 POST ilman 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:

    1. Luo kertakäyttöinen testisalaisuus, jossa on asetus ”Tuhoa lukemisen jälkeen” päällä.
    2. Hae se päätteestä. Juuri näin esikatselutoiminto toimii: curl -s -o /dev/null -w "%{http_code}" "<url>"
    3. 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.
    4. Jos salaisuus säilyi, näytä se kunnolla selaimessa ja aja curl uudelleen. 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.

    Avaa työkalu

    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.

    Lähteet

    Työkalut tähän artikkeliin

    Usein kysytyt kysymykset

    LinkPilot lähettää salaisen linkin, joka katoaa, kun se on avattu.

    Salataan selaimessasi, näytetään kerran ja tuhotaan sitten. Tiliä ei tarvita.

    Luo salainen linkki

    Lue seuraavaksi