När webbcachen visar personligt innehåll för fel besökare
En butiksägare får ett mejl från en kund som är less. Kunden är inloggad på sajten men ser fel pris, eller i värsta fall en annan kunds namn och varukorg i stället för sin egen. Det är sällan ett hackerangrepp, oftast något betydligt mer vardagligt. En delad cache har sparat undan en enda besökares personliga sida och sedan levererat precis samma kopia till alla som kommer efter.
Delad cache och privat cache, en avgörande skillnad
Caching finns på flera nivåer samtidigt, hos webbhotellet, i en CDN-tjänst och i webbläsaren själv. Standarden för HTTP-cachning, RFC 9111, gör en tydlig åtskillnad mellan två typer av cache.
"A shared cache is a cache that stores responses for reuse by more than one user; shared caches are usually (but not always) deployed as a part of an intermediary. A private cache, in contrast, is dedicated to a single user; often, they are deployed as a component of a user agent."
En privat cache, till exempel i din egen webbläsare, får gärna spara din inloggade vy. Problemet uppstår när en delad cache, en som ligger mellan servern och alla besökare samtidigt, sparar en sida som egentligen bara var avsedd för en person. Direktivet Cache-Control: private "indicates that a shared cache MUST NOT store the response", just för att markera att sidan hör till en enskild användare. Vill du repetera grunderna har vi gått igenom dem i vår FAQ om vad caching är. Den här guiden går i stället in på ett specifikt sätt caching kan gå fel, oavsett vilket webbhotell du använder.
Cookies räddar dig inte automatiskt
Den kontraintuitiva delen är att en cookie i sig inte skyddar dig. Att vara inloggad eller ha en giltig sessionscookie ger ingen automatisk garanti för din egen version av en sida. Cachen bryr sig bara om det den är konfigurerad att bry sig om, och har ingen talat om att en sida varierar per besökare behandlar den alla likadant. Cloudflare varnar för exakt det här i sin dokumentation om inställningen "Cache Everything", som cachar hela HTML-svaret oavsett innehåll.
"This option caches all HTML regardless of the presence of dynamic content. If you use this approach to cache pages containing dynamic content, visitors may receive information not intended for them."
Cloudflare, som annars är ett bra verktyg mot bland annat överbelastningsattacker, blir riskabelt att slå på för brett. Bolagets egen felsökningsguide för inloggningsproblem är lika tydlig, "Do not cache login pages or other authenticated HTML".
Varukorgen är sällan problemet, allt annat personligt är det
Den goda nyheten är att kundvagn och kassa i de flesta moderna lösningar redan är pålitligt undantagna från cachning, ett gammalt och välkänt problem som de flesta plattformar och plugin har lärt sig hantera. Faran ligger i stället i allt personligt innehåll som sitter utanför den vagnen, ett medlemspris, ett grossistpris för återförsäljare, en personlig hälsning med förnamn högst upp på sidan, en rekommendationslista byggd på tidigare köp. Ingen av de här delarna har samma självklara skyddsstatus som kassan, och det är där de flesta läckor faktiskt uppstår.
Skilj på ett personligt läckage och en gammal sida
Det är lätt att blanda ihop det här med ett annat, mer känt cacheproblem, nämligen att en föråldrad version av sidan visas efter en uppdatering, till exempel en gammal pristext som dröjer sig kvar. Det problemet handlar om tid. Problemet den här guiden handlar om är ett annat, här visas inte en gammal version utan en annan besökares aktuella, personliga version. Skillnaden spelar roll när du felsöker. Föråldrat innehåll löser du med kortare livstid, ett läckage löser du genom att märka innehållet som privat eller variera cachenyckeln, se längre ner.
Den svenska grossistscenen där det märks tydligast
Ett konkret svenskt exempel är en WooCommerce-butik med rollbaserade priser, där inloggade återförsäljare ser ett grossistpris och konsumenter ser konsumentpriset på samma produktsida. Cachas sidan delat, utan att cachen känner till besökarens roll, kan ett inköpspris läcka ut till en konsument, eller tvärtom. Vi går igenom serverkraven för den typen av butik i vår genomgång av webbhotell för WooCommerce.
LiteSpeeds egen utvecklardokumentation beskriver just den här situationen som exempel på när man behöver en så kallad vary-cookie, en inställning som talar om för cachen att spara flera versioner av samma sida beroende på ett cookievärde.
"A membership plugin shows one set of shop prices to members and a different set of prices to non-members. This means that there are two versions of each page, based on the value of the plugin's _member cookie. In order to store both of those versions in the cache, you will want LSCache to create a vary based on the _member cookie."
Utan den inställningen ser cachen bara en enda sida att spara, och den sidan blir vad som slumpmässigt hamnade i cachen först, medlemspris eller vanligt pris.
Så kontrollerar du din egen sajt
Du behöver inte gissa. Tre enkla kontroller avslöjar om en personlig sida riskerar att delas mellan besökare.
- Öppna webbläsarens nätverksflik (F12, fliken Network) och ladda en inloggad sida. Leta efter cache-headern, till exempel
x-litespeed-cache,cf-cache-statuseller Loopiasx-cache. - Öppna samma sida i ett andra, inkognitofönster utan att vara inloggad, och jämför innehållet rad för rad.
- Läs leverantörens egna varningar innan du aktiverar cache. Loopia skriver det rakt ut i sin dokumentation för Boost Sidcache.
"Observera att vissa sidor inte ska cachas, till exempel sidor där man loggar in eller sidor som visar olika innehåll för olika besökare. Kontrollera sådana sidor noga efter att du har aktiverat cachen, så att du är säker på att de inte cachas."
Loopias x-cache-header kan visa fyra värden. HIT betyder en cachad sida, MISS en sida som inte var cachad, STALE en cachad sida vars livstid gått ut, och BYPASS att cachen medvetet förbigås. Värt att komma ihåg är att sidcache hos de stora svenska LiteSpeed-hotellen och i Loopia Boost normalt levereras avstängd som standard, du slår på den själv i kontrollpanelen. Hos Oderland var det, enligt bolagets egen supportguide från 2022, inaktiverat direkt efter att LiteSpeed-pluginet installerats, en opt-in-modell som liknar Loopias. Rätt fråga är därför oftast "vad behöver jag kontrollera innan jag aktiverar cache", inte "läcker min sajt just nu".
Fixordförrådet att ge din utvecklare eller värd
Lösningen är i praktiken aldrig att stänga av cachen helt, det vore att kasta bort hastighetsvinsten för ett problem som går att rikta in mer exakt. Poängen är att märka det personliga innehållet rätt, så att cachen vet vad den får dela. Några begrepp att ta upp med din utvecklare eller webbhotellets support.
- Cache-Control: private i svarshuvudet, standardiserat i RFC 9111, markerar att just den sidan hör till en enskild besökare.
- Vary på en medlems- eller inloggningscookie, som i LiteSpeeds exempel ovan, gör att cachen sparar flera versioner av samma sida i stället för en enda delad kopia.
- ESI, hole punching, en teknik där en liten del av sidan, till exempel en hälsning med förnamn, lämnas utanför cachen medan resten cachas som vanligt, precis som LiteSpeeds dokumentation sammanfattar det. "With ESI we can cache this page while punching a hole for line 2 and leaving that content uncached."
- Bypass on cookie hos Cloudflare, en regel som hoppar över cachen helt så fort en viss cookie finns i förfrågan, till exempel en inloggningscookie.
- Ingen cachning av svar med Set-cookie, vilket är hur Loopia Boost Sidcache redan är byggt. Finns en Set-cookie-header i svaret cachas sidan inte alls.
Ett släktskap värt att känna till är objektcache, till exempel Redis, som cachar databasfrågor snarare än hela sidor och inte läcker på samma sätt, men ämnet är brett nog för ett eget resonemang. Utöver oavsiktliga läckor finns det också en avsiktlig attackvariant av samma mekanism, känd som web cache deception, där en angripare medvetet lurar cachen att spara en sida den inte borde. Den tekniken ligger utanför ramen för den här guiden.
GDPR-frågan värd att ställa
En läckt personlig hälsning eller kontouppgift är, strikt sett, personuppgifter som visats för fel mottagare. Det gör frågan relevant att ta upp med din utvecklare eller ditt webbhotell, snarare än att avfärda den som ett tekniskt kuriosum. Det är ingen juridisk slutsats i sig, bara en fråga värd att ställa innan du aktiverar en delad cache på en sajt med inloggade användare, medlemspriser eller personifierat innehåll.