SECVRA

Säkerhetsheaders för webbplatser: CSP, HSTS m.fl. – en praktisk guide

Så skyddar säkerhetsheaders er webbplats mot XSS, clickjacking och avlyssning. Konkreta värden för CSP, HSTS, Referrer-Policy m.fl. och hur ni sätter dem.

Skribent
Secvra säkerhetsteam
Publicerad
Lästid
7 min

Varje gång någon besöker er webbplats skickar servern med en uppsättning instruktioner till webbläsaren, så kallade HTTP-headers. Några av dem finns enbart för att skydda besökaren: de talar om att sidan bara får laddas över HTTPS, att den inte får bäddas in på andra sajter och vilka skript som får köras.

Säkerhetsheaders är ett av de billigaste skydd som finns. Ofta räcker några rader konfiguration, och de skyddar mot hela kategorier av attacker som cross-site scripting (XSS), clickjacking och avlyssning. Ändå saknas de på väldigt många svenska webbplatser – det är en av de vanligaste bristerna vi ser när vi granskar sajter.

Den här guiden går igenom de headers som gör skillnad, vilka värden vi rekommenderar och hur ni sätter dem i Apache, nginx och andra miljöer.

Snabböversikt

HeaderSkyddar motRekommenderat startvärde
Strict-Transport-SecurityNedgradering till okrypterad HTTP, avlyssningmax-age=31536000; includeSubDomains
Content-Security-PolicyXSS, inskjutna skript, clickjackingSe avsnittet nedan – börja i rapportläge
X-Content-Type-OptionsAtt webbläsaren gissar filtyp (MIME sniffing)nosniff
X-Frame-OptionsClickjacking i äldre webbläsareDENY eller SAMEORIGIN
Referrer-PolicyAtt adresser med känslig information läcker till andra sajterstrict-origin-when-cross-origin
Permissions-PolicyMissbruk av kamera, mikrofon, position m.m.camera=(), microphone=(), geolocation=()
Cross-Origin-Opener-PolicyAttacker via fönster som öppnas från andra sajtersame-origin

Strict-Transport-Security (HSTS)

HSTS talar om för webbläsaren att er domän bara får besökas över HTTPS under en viss tid. Utan HSTS kan den första förfrågan gå över okrypterad HTTP innan omdirigeringen till HTTPS sker – och den förfrågan kan en angripare på samma nätverk, till exempel ett öppet wifi, avlyssna eller manipulera.

Strict-Transport-Security: max-age=31536000; includeSubDomains
  • max-age anger i sekunder hur länge regeln gäller. 31536000 är ett år.
  • includeSubDomains gäller regeln även alla underdomäner. Kontrollera först att alla underdomäner verkligen fungerar över HTTPS, även interna och gamla.
  • preload låter er ansöka om att domänen byggs in i webbläsarna via hstspreload.org. Kraven är bland annat giltigt certifikat, omdirigering från HTTP till HTTPS, HTTPS på alla underdomäner och ett max-age på minst ett år. Det är svårt att ångra – borttagning kan ta månader – så ansök bara om ni är säkra på att hela domänen ska vara HTTPS för all framtid.

Så inför ni HSTS säkert: börja med max-age=300 (fem minuter), kontrollera att inget går sönder, öka till en vecka och sedan till ett år.

Content-Security-Policy (CSP)

CSP är den mest kraftfulla säkerhetsheadern, och den mest krävande. Den anger exakt varifrån sidan får ladda skript, stilmallar, bilder och typsnitt, och om inbäddade skript får köras. Lyckas en angripare skjuta in ett skript via ett formulärfält eller ett sårbart tillägg kan en bra CSP hindra webbläsaren från att köra det.

Den starkaste varianten bygger på engångsvärden, så kallade nonces, som servern genererar för varje sidvisning:

Content-Security-Policy: script-src 'nonce-R4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'

Det kräver att varje <script>-tagg får rätt nonce, vilket är enkelt i moderna ramverk men ofta svårt i WordPress-sajter med många tillägg som lägger in egna skript. Har ni en sådan sajt kan ni börja med de direktiv som nästan aldrig ställer till problem:

Content-Security-Policy: object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; upgrade-insecure-requests

Det stoppar inte all XSS, men det skyddar mot clickjacking, mot att formulär skickas till främmande servrar och mot att gamla insticksprogram missbrukas – och det är ett första steg mot en strängare policy.

Börja alltid i rapportläge

Med headern Content-Security-Policy-Report-Only tillämpas inte policyn, men webbläsaren rapporterar allt som skulle ha blockerats. Låt den gå i rapportläge i ett par veckor, gå igenom rapporterna och justera policyn innan ni slår på den skarpt. Enligt MDN bör ni ange både det nyare direktivet report-to och det äldre report-uri, eftersom stödet för report-to ännu inte är fullständigt i alla webbläsare.

X-Content-Type-Options

X-Content-Type-Options: nosniff

Hindrar webbläsaren från att gissa vilken typ av fil den fått. Utan headern kan en uppladdad fil som ser ut som en bild i stället tolkas och köras som skript. En rad, inga biverkningar – den ska finnas på alla sajter.

Skydd mot clickjacking: frame-ancestors och X-Frame-Options

Clickjacking innebär att er sida laddas osynligt i en ram på en annan sajt, så att besökaren luras att klicka på knappar på er sida utan att veta om det. Det moderna skyddet är CSP-direktivet frame-ancestors. Den äldre headern X-Frame-Options behövs bara för riktigt gamla webbläsare, men skadar inte:

Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN

Använd 'none' respektive DENY om sidan aldrig ska bäddas in någonstans, inte ens på er egen sajt.

Referrer-Policy

När en besökare klickar på en länk till en annan sajt skickar webbläsaren normalt med adressen till sidan hen kom ifrån. Innehåller adressen ett ordernummer, en återställningslänk för lösenord eller en sökning kan den informationen hamna hos tredje part. Moderna webbläsare har ett rimligt standardvärde, men OWASP rekommenderar att ni sätter policyn uttryckligen:

Referrer-Policy: strict-origin-when-cross-origin

Det betyder att andra sajter bara får se er domän, aldrig hela adressen.

Permissions-Policy

Styr vilka webbläsarfunktioner sidan och inbäddat innehåll får använda. Stäng av det ni inte använder:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

Använder ni en betalningslösning som kräver Payment Request API ska ni förstås inte stänga av payment.

Cross-Origin-Opener-Policy

Cross-Origin-Opener-Policy: same-origin

Isolerar er sida från fönster som öppnats av andra sajter och skyddar mot en del avancerade attacker. Om ni använder inloggning eller betalning som öppnas i ett popup-fönster kan same-origin-allow-popups behövas i stället – testa flödet innan ni sätter headern i produktion.

Headers ni ska ta bort

Lika viktigt som att lägga till rätt headers är att ta bort de som berättar för mycket:

  • X-Powered-By avslöjar ofta PHP-version eller ramverk, till exempel PHP/7.4.33. Stäng av den med expose_php = Off i php.ini. Det är precis den här typen av information som automatiserade attacker letar efter när en ny sårbarhet publiceras.
  • Server visar ibland webbserverns exakta version. Sätt ServerTokens Prod i Apache eller server_tokens off; i nginx.
  • X-XSS-Protection styrde ett filter som moderna webbläsare har tagit bort. OWASP rekommenderar att inte skicka headern alls, eller sätta den till 0.
  • Expect-CT är avvecklad. OWASP skriver kort: använd den inte.

Glöm inte cookies

Cookies som håller användare inloggade bör ha flaggorna Secure (skickas bara över HTTPS), HttpOnly (kan inte läsas av JavaScript) och SameSite=Lax eller Strict (skickas inte med i förfrågningar från andra sajter). För sessionscookies kan prefixet __Host- dessutom låsa cookien till exakt er domän.

Så sätter ni headers

Apache (.htaccess eller virtuell värd)

<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header always set X-Content-Type-Options "nosniff"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
  Header always unset X-Powered-By
</IfModule>

nginx

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

En vanlig fallgrop i nginx: add_header ärvs bara från en högre nivå om det inte finns någon add_header alls i det aktuella location-blocket. Lägger ni till en enda header i ett location-block försvinner alla de andra där. Parametern always gör att headern skickas även vid felsidor.

Webbhotell, Cloudflare och WordPress

På delade webbhotell fungerar oftast .htaccess-exemplet ovan. Använder ni Cloudflare kan ni sätta headers med regler för att ändra svarsheaders, utan att röra servern. I WordPress finns tillägg för säkerhetsheaders, men vi föredrar att sätta dem på servernivå – då gäller de även filer som inte går genom WordPress, och de försvinner inte om tillägget avaktiveras.

Testa resultatet

  • Terminalen: curl -sI https://er-sajt.se visar alla headers servern skickar.
  • Webbläsaren: öppna utvecklarverktygen, välj fliken Nätverk, ladda om och klicka på den första förfrågan.
  • Mozillas HTTP Observatory ger betyg och förklaringar.
  • Secvras gratis skanning kontrollerar headers tillsammans med TLS, programversioner och e-postskydd för domänen.

Testa alltid de viktigaste flödena – inloggning, kassa, betalning och formulär – efter varje ändring.

I vilken ordning ska ni göra det?

  1. Ta bort X-Powered-By och versionsnummer i Server.
  2. Lägg till X-Content-Type-Options, Referrer-Policy, Permissions-Policy och skydd mot inramning.
  3. Inför HSTS med kort max-age och öka stegvis.
  4. Inför en grundläggande CSP och kör en strängare variant i rapportläge.
  5. Gå igenom rapporterna och skärp CSP:n när ni vet vad sajten behöver.

Steg 1–3 tar sällan mer än en timme för den som har tillgång till serverkonfigurationen. Vill ni ha hjälp, eller veta hur er sajt står sig i övrigt, ingår headers i vår säkerhetsgranskning av webbplatser med fast pris. Ni kan också börja med en gratis skanning och läsa vidare om varför gamla PHP-versioner är en av de vanligaste riskerna vi hittar.

Källor

Vanliga frågor

Vilka säkerhetsheaders är viktigast?

Börja med Strict-Transport-Security (HSTS), X-Content-Type-Options: nosniff, skydd mot inramning via frame-ancestors eller X-Frame-Options och en uttrycklig Referrer-Policy. Content-Security-Policy ger störst skydd mot XSS men kräver mer arbete och bör införas stegvis.

Kan säkerhetsheaders göra att webbplatsen slutar fungera?

Ja, främst Content-Security-Policy och HSTS med preload. Därför ska CSP först köras i rapportläge (Content-Security-Policy-Report-Only) och HSTS införas med ett kort max-age som ni sedan ökar.

Behöver vi fortfarande X-XSS-Protection?

Nej. Moderna webbläsare har tagit bort funktionen och OWASP rekommenderar att ni antingen inte skickar headern alls eller sätter den till 0. Skyddet mot XSS ska i stället komma från en Content-Security-Policy och korrekt kodning av utdata.

Hur testar jag vilka headers min webbplats skickar?

Kör curl -sI https://er-sajt.se i en terminal, titta under fliken Nätverk i webbläsarens utvecklarverktyg, använd Mozillas HTTP Observatory eller kör Secvras gratis säkerhetsskanning.

  • säkerhetsheaders
  • CSP
  • HSTS
  • webbsäkerhet
  • Apache
  • nginx
  • WordPress