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
- Kategori
- Webbsäkerhet
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
| Header | Skyddar mot | Rekommenderat startvärde |
|---|---|---|
| Strict-Transport-Security | Nedgradering till okrypterad HTTP, avlyssning | max-age=31536000; includeSubDomains |
| Content-Security-Policy | XSS, inskjutna skript, clickjacking | Se avsnittet nedan – börja i rapportläge |
| X-Content-Type-Options | Att webbläsaren gissar filtyp (MIME sniffing) | nosniff |
| X-Frame-Options | Clickjacking i äldre webbläsare | DENY eller SAMEORIGIN |
| Referrer-Policy | Att adresser med känslig information läcker till andra sajter | strict-origin-when-cross-origin |
| Permissions-Policy | Missbruk av kamera, mikrofon, position m.m. | camera=(), microphone=(), geolocation=() |
| Cross-Origin-Opener-Policy | Attacker via fönster som öppnas från andra sajter | same-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-ageanger i sekunder hur länge regeln gäller. 31536000 är ett år.includeSubDomainsgäller regeln även alla underdomäner. Kontrollera först att alla underdomäner verkligen fungerar över HTTPS, även interna och gamla.preloadlå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 ettmax-agepå 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-Byavslöjar ofta PHP-version eller ramverk, till exempelPHP/7.4.33. Stäng av den medexpose_php = Offi php.ini. Det är precis den här typen av information som automatiserade attacker letar efter när en ny sårbarhet publiceras.Servervisar ibland webbserverns exakta version. SättServerTokens Prodi Apache ellerserver_tokens off;i nginx.X-XSS-Protectionstyrde ett filter som moderna webbläsare har tagit bort. OWASP rekommenderar att inte skicka headern alls, eller sätta den till0.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.sevisar 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?
- Ta bort
X-Powered-Byoch versionsnummer iServer. - Lägg till
X-Content-Type-Options,Referrer-Policy,Permissions-Policyoch skydd mot inramning. - Inför HSTS med kort
max-ageoch öka stegvis. - Inför en grundläggande CSP och kör en strängare variant i rapportläge.
- 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
- OWASP: HTTP Security Response Headers Cheat Sheet
- MDN: Content-Security-Policy, Content-Security-Policy-Report-Only och Strict-Transport-Security
- hstspreload.org – krav för HSTS preload
- web.dev: Mitigate cross-site scripting with a strict Content Security Policy
- nginx: dokumentation för add_header
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