Försvar på djupet: Del 6 Webbläsare

This page is also available in: 🇺🇸 English
19 September 2023

I föregående artikel diskuterade vi viktiga säkerhetsaspekter i din infrastruktur med fokus på serversidan. I denna artikel kommer vi att diskutera de utmaningar vi har på klientsidan i allmänhet, och i webbläsare i synnerhet.

Webbläsare är en attraktiv målmiljö för att leverera system till användare. Den är lättåtkomlig och kräver ingen installation, de allra flesta användare har idag tillgång till en modern webbläsare. För användaren ger den, jämfört med en installation av en applikation, en väl avgränsad miljö att köra applikationer i.

Webbläsarens avgränsade miljö ger ett skydd mot t ex ransomware eller andra attacker som försöker komma åt datorns resurser. Mobilapplikationer ger i teorin också ett skydd, men i praktiken installeras de ofta med tillgång till många av de känsliga funktioner och data som finns på telefonen, som t ex mikrofon, kamera, position och adressbok.

För utvecklare blir språk, ramverk och verktyg för att bygga webbapplikationer allt bättre och gör det möjligt att leverera ett system som idag är i det närmaste oberoende av vilken typ av klient användaren har. Men från applikationens perspektiv representerar webbläsaren en svår säkerhetsutmaning för oss utvecklare där vi vill lyfta fram följande:

Detta är egenskaper och förutsättningar som är mycket viktiga att förstå som applikationsutvecklare. I fallet med en traditionell applikation är din miljö ur ett applikationsperspektiv isolerad i en egen process med en statisk runtime. Även om man också för en traditionell applikation har liknande säkerhetsutmaningar och behöver hantera beroenden till tredjeparts-komponenter och ramverk (så som Java och .NET) är problemet större i en webbläsare.

Ett säkert API på serversidan är A och O i ett säkert system. Du kan inte tillföra säkerhet i dina klienter för ditt API. En angripare kan alltid attackera ditt API utan att använda klienten alls.

Plugins

Moderna webbläsare har en modell för plugin där de vid installation frågar användaren efter tillstånd att läsa eller ändra data. Många användare godkänner dessa rättigheter utan att fundera närmare på vad det betyder för just din webbapplikation. Vi har inga som helst möjligheter att skydda vår webbapplikation mot en angripare som lyckas få användaren att installera en plugin som fått rättighet att agera proxy för all trafik genom webbläsaren. En sådan angripare kan utnyttja vårt API med precis samma rättigheter som användaren har.

Pontus Hanssen, Omegapoint

Hur ser du till att dina användare inte installerar och använder en sårbar webbläsare eller plugin?

Vi bör använda de säkerhetsfunktioner som moderna webbläsare ger oss. Som utgångspunkt stödjer vi enbart de versioner av webbläsare som uppdateras och har support från tillverkaren. Ibland kan det finnas marknadsmässiga krav och lagar som styr vilka webbläsare vi måste stödja, men vi behöver bedöma riskerna och förstå att vi kompromissar med hur stark säkerhet vi kan ge våra användare.

Kasper Karlsson, Omegapoint

Vi har sett många exempel på sårbara plugins genom åren. Adobes plugin för att visa PDF i Mozilla Firefox hade en Universal XSS (UXSS) som möjliggjorde attacker för alla webbsidor som visade PDF filer. Detta var runt 2007, så sårbara plugins är inte nytt!

Delad miljö

Webbläsare är en delad miljö där webbapplikationer från olika webbplatser, med olika ursprung, körs tillsammans. Det är med andra ord inte en isolerad process på samma sätt som en traditionell applikation. Det grundläggande skyddet för att separera t ex en banks webbplats från osäkra webbplatser med skadlig kod, kallas Same Origin Policy (SOP).

SOP är sedan länge implementerad i alla moderna browsers och är ett så grundläggande skydd att vi ofta tar det för givet. Kanske är det något som applikationsutvecklare aldrig haft behov av att förstå på djupet då det alltid finns där. Principen som är viktig att förstå är att webbplatser i grunden separeras av: protokoll, värd och port. Att förstå alla detaljer fullt ut är en utmaning och beskrivs i detalj på https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy

Enligt vår erfarenhet är det ett vanligt misstag att man öppnar upp SOP genom att använda en CORS konfiguration som öppnar för alla HTTP verb, oavsett ursprung. Detta ökar risken för lyckade CSRF-attacker.

Det är viktigt att förstå att CORS endast är ett skydd i webbläsaren, ej en “brandvägg” på server-sidan. Om du använder en arkitektur med en BFF som vi diskuterat i tidigare artikel undviker du problematiken eftersom all trafik kommer från samma ursprung (origin).

Brister i webbläsare

Över tid har det även funnits brister i webbläsare, ibland till och med i hur de implementerat SOP. Förutom rena säkerhetsbuggar, är inkonsekvent beteende i olika webbläsare något som de flesta någon gång har haft problem att hantera. Ofta är det ofarligt, t ex hur ett visst element renderas eller stöds. Ibland ger dock ett ändrat beteende upphov till allvarliga problem som är svåra att upptäcka då man ofta inte har resurser att kontinuerligt testa i alla webbläsare som behöver stödjas.

Ett exempel är hur Cookie Policies under åren förändrats i tolkningen av Strict, Lax och None.

Cookie Policy erbjuder i moderna webbläsare ett skydd mot CSRF attacker. Men då stödet varierat över tid är det fortsatt motiverat att använda tekniker som ej är beroende av stödet i browsern, t ex “double-submit-cookies” och återautentisering av användaren, se mer i sektionen Säkra session i Klienter och sessioner.

Notera även att Google och Apple omkring 2020 införde ändringar i sina browsers för att gör det svårare att spåra användare mellan olika siter med hjälp av “third-party-cookies” se t ex https://blog.chromium.org/2019/10/developers-get-ready-for-new.html eller https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/

Dynamisk runtime

Även om vi hanterar allt vi hittills lyft fram så är javascripts dynamiska runtime fortsatt en svår utmaning. Du kan ha gjort allting rätt:

Men du kan ändå ha en Cross-Site Scripting (XSS) -sårbarhet som gör det möjligt för en angripare att t ex gå förbi behörighetskontroll och nå data som den inte skall ha tillgång till.

En XSS betyder att en angripare kan köra script i applikationen, och därmed får samma rättigheter till data som användaren har. Sårbarheten uppstår när applikationen exempelvis renderar osäkert, inkommande data som HTML. Lösningen är att korrekt utdatakoda data för den kontext som den ska användas i.

Att vårt API har korrekt indata-validering är ett första försvar mot XSS, men är absolut inte tillräckligt, det minskar endast attack vektorn. Lösningen för grundproblemet med XSS är alltid rätt utdatakodning på klienten.

Tyvärr är detta lättare sagt än gjort. Även om vi har ett ramverk som stödjer rätt utdatakodning för rätt kontext (javascript, html eller css) samt ej tvingar på oss “eval-konstruktioner”, är det ibland svårt att använda funktionerna rätt och man kanske använder sig av “raw-html-funktioner” för att få specifik formatering att rendera korrekt.

Notera likheten med en injection-attack mot servern, t ex SQLi. Samt kopplingen till Domän Driven Design (DDD) och säkerhet (DDSec) som hjälper dig identifiera domängränser och var du behöver utdatakodning och indatavalidering.

En djupare förklaring kring dessa koncept finns i artikel Secure APIs by design och boken Secure by Design.

Det är viktigt att använda de skydd som finns för att minska risken för XSS och begränsa skadan i sådana attacker. Idag görs detta genom att definiera en Content Security Policy (CSP), som stöds i alla moderna webbläsare. Det är, precis som CORS, ett skydd i webbläsaren och tillför ingen säkerhet på serversidan.

Notera att det kan vara en utmaning att skriva en stark CSP som stöds av alla webbläsare. Ett verktyg som kan hjälpa dig att hitta brister i din CSP är CSP Evaluator från Google: https://csp-evaluator.withgoogle.com/

Adrian Bjugård, Omegapoint

Jag ser ofta att man öppnar upp i sin CSP för att använda komponenter som kräver tex unsafe-eval. Istället bör man kanske tänka igenom sitt val av komponent, eller verkligen kolla att man använder den på ett säkert sätt.

Jag ser också att man tar bort sin CSP för att förenkla i utvecklingsarbetet. Ibland följer detta tyvärr med ut i produktionsmiljön. Försök ha koll på hur din CSP ser ut i produktion. Kanske går det att skriva ett systemtest som veriferar att den finns och inte innhåller en unsafe-eval?

CSP är en säkerhetsstandard som låter applikationen definiera en lista av godkända källor för webbläsaren att hämta innehåll och skript ifrån när den exekverar din applikation. Du bör implementera en restriktiv CSP för din applikation enligt principen Least Privilege, genom att vara explicit när du definierar din CSP och specificera enbart de nödvändiga källorna för varje innehållstyp som används av applikationen. Sträva efter en systemdesign där du bara i undantagsfall behöver specificera källor som du själv inte kontrollerar. Hur detta görs i detalj beskrivs på https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

En stark CSP har ofta default-src direktivet satt till enbart källvärdet none, vilket innebär att om inte andra direktiv specifierar källvärden så faller vi tillbaka på none. Då får inga skript användas i applikationen alls, oavsett deras ursprung. Det är bättre att uppdatera din CSP ofta än att öppna upp för onödiga källor från början. Du behöver alltså förstå SOP för att implementera en bra CSP.

Notera att i en delad miljö som du inte kontrollerar fullt ut, innebär källvärdet ‘self’ att skript från samma ursprung som dig själv kan köras som en del av din applikation.

Låt dig inte luras att en stark CSP räcker för att skydda din applikation mot XSS. En CSP begränsar attackytan när någon del av applikationen är felaktigt utdatakodad, den ger inte ett komplett XSS-skydd.

Använd med fördel CSP-specifikationens rapporteringsläge för att få reda på om du har någon komponent som behöver anpassas innan du implementerar en CSP. Att ta hand om CSP-rapporter på ett säkert sätt är dock en utmaning. De kan bli väldigt många, innehålla XSS-attacker, och det kan vara svårt att i ett stort produktionssystem bedöma om någon klient är utsatt för en attack.

Förutom XSS-skydd genom CSP-direktiv så finns även ett antal andra headers för andra typer av skydd bl a Referer-policy och Feature-policy.

Ett bra verktyg för att testa vilka skydd som är aktiva i sin applikation är https://securityheaders.com/

Sammanfattning

Vi har alltså många skydd för användaren som vi kan använda oss av i en modern webbläsare, vilka utgör en del av en arkitektur där vi bygger säkerhet i flera lager (“Defense in Depth”).

Det finns en avvägning att göra mellan tillgänglighet, konfidentialitet och integritet. Ett exempel där det kan vara svårt att hitta en bra balans är för att möta krav om analyser av användarbeteende. Det finns många olika verktyg, bl a “Tag Managers”, som på ett mycket kraftfullt sätt gör det möjligt att i detalj följa en användare. Det kan vara bra ur ett analysperspektiv, men för med sig flera säkerhetsmässiga utmaningar:

Det är viktigt att förstå dessa egenskaper och reda ut frågorna innan man använder den här typen av tjänster.

Webbläsare är en attraktiv målmiljö, som för det allra flesta system på mycket goda grunder är ett förstahandsval. Men det är viktigt att förstå dess förutsättningar och säkerhetsmodell. Fundera över valet av klienttyp. För vissa applikationer med höga säkerhetskrav kanske inte webbläsaren är det bästa valet?

I nästa artikel avslutar och ger vi en summering av serien.

Se Defense in Depth för ytterligare material och kodexempel kopplade till den här artikelserien.


Fler artiklar i serien: