Guides 6 min. leestijd

HTTP-securityheaders: wat je stuurt en hoe je ze controleert

HSTS, Content-Security-Policy, X-Content-Type-Options, framebeveiliging, Referrer-Policy en Permissions-Policy: wat elke header doet en een veilige waarde om mee te beginnen.

HTTP-securityheaders zijn responseheaders die de browser opdragen beveiligingen toe te passen die je site anders zelf zou moeten bouwen: op HTTPS blijven, alleen scripts uitvoeren van bronnen die je vertrouwt, geen bestandstypen raden, andere sites de pagina niet in een frame laten tonen en minder prijsgeven over waar een bezoeker vandaan kwam. Zes headers dekken het grootste deel: Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, X-Frame-Options (of frame-ancestors in de CSP), Referrer-Policy en Permissions-Policy. De meeste kosten één regel serverconfiguratie. Content-Security-Policy vraagt om planning, dus begin die in Report-Only-modus.

De zes headers in één oogopslag

HeaderBeschermt tegenWaarde om mee te beginnenStrict-Transport-SecurityVerbindingen die worden teruggezet naar gewoon HTTPmax-age=31536000Content-Security-PolicyGeïnjecteerde scripts (cross-site scripting)Een policy die eerst in Report-Only is getestX-Content-Type-OptionsBrowsers die een bestand als een ander type uitvoerennosniffX-Frame-Options of frame-ancestorsClickjacking via framesSAMEORIGIN en frame-ancestors ‘self’Referrer-PolicyVolledige adressen die naar andere sites lekkenstrict-origin-when-cross-originPermissions-PolicyOngebruikte browserfuncties die worden aangevraagdcamera=(), microphone=(), geolocation=()

Strict-Transport-Security (HSTS)

Ook met een redirect van HTTP naar HTTPS doet een bezoeker die je domein intypt het eerste verzoek via gewoon HTTP, waar het onderweg kan worden meegelezen of aangepast. HSTS vertelt de browser om vanaf dan HTTPS te gebruiken voor je domein, zo lang als max-age aangeeft, in seconden:

Strict-Transport-Security: max-age=31536000

Begin kort. Browsers houden zich aan HSTS tot het verloopt, dus een fout blijft hangen. Probeer een paar dagen max-age=300 en verhoog het dan naar een jaar (31536000).

includeSubDomains past de regel toe op elk subdomein. Voeg het alleen toe als ze allemaal via HTTPS werken, ook oude staging- en mailhosts.

preload vraagt via hstspreload.org om het domein in browsers in te bouwen. Daarvoor zijn een max-age van minstens een jaar en includeSubDomains vereist. Verwijderen duurt lang, dus beschouw het als permanent.

Browsers negeren HSTS in gewone HTTP-responses. Stuur het via HTTPS en houd het certificaat in de gaten: met HSTS kan een bezoeker een verlopen certificaat niet meer wegklikken.

Content-Security-Policy

Een Content Security Policy legt vast waarvandaan de pagina scripts, stijlen, afbeeldingen, lettertypen en frames mag laden, en waarnaartoe formulieren mogen versturen. Krijgt een aanvaller een script in de pagina, via een reactieveld of een gecompromitteerde plugin, dan weigert de browser het uit te voeren, tenzij de policy de bron toestaat. Het is de krachtigste van de zes en de makkelijkste om verkeerd te doen: een te strenge policy breekt je eigen analytics, chatwidget of betaalformulier.

Begin met Report-Only

Stuur de policy eerst als Content-Security-Policy-Report-Only. De browser dwingt dan niets af, maar meldt elke overtreding aan het adres in report-uri (of het nieuwere report-to) en logt die in de console. Een startpunt voor een site die zijn eigen bestanden serveert:

Content-Security-Policy-Report-Only: default-src ‘self’; script-src ‘self’; style-src ‘self’; img-src ‘self’ data:; font-src ‘self’; object-src ‘none’; base-uri ‘self’; form-action ‘self’; frame-ancestors ‘self’; report-uri /csp-report

Klik door de site, verzamel een week of twee de meldingen en voeg de bronnen toe die je echt gebruikt, zoals je analytics- of betaalprovider. Tonen de meldingen alleen nog dingen die je geblokkeerd wilt hebben, stuur dan dezelfde policy als Content-Security-Policy.

Inline scripts: nonces, geen unsafe-inline

Een strenge policy blokkeert inline <script>-blokken en onclick-attributen, omdat een geïnjecteerd script er precies zo uitziet. Met ‘unsafe-inline’ verdwijnt het probleem, maar ook het grootste deel van de bescherming. Geef in plaats daarvan elke response een willekeurige nonce, zet die in de policy als ‘nonce-…’ en zet dezelfde waarde op je eigen scripttags; verplaats inline event handlers naar scriptbestanden. Tagmanagers die hun eigen scripts injecteren vragen extra aandacht, want elke tag die ze toevoegen moet ook zijn toegestaan.

X-Content-Type-Options

Browsers raadden vroeger het type van een bestand aan de hand van de inhoud als de Content-Type van de server niet leek te kloppen. Zo kon een geüploade ‘afbeelding’ als script worden uitgevoerd. X-Content-Type-Options: nosniff zet dat raden uit. De header heeft één geldige waarde en geen bekend nadeel, zolang je server de juiste contenttypes stuurt, dus stuur hem mee met elke response.

X-Frame-Options en frame-ancestors

Bij een clickjacking-aanval toont een andere site jouw pagina in een onzichtbaar frame en verleidt bezoekers om op knoppen daarin te klikken. X-Frame-Options: DENY verbiedt elk frame, en SAMEORIGIN staat alleen je eigen pagina’s toe. De CSP-directive frame-ancestors doet hetzelfde met meer controle, zoals een lijst met partnersites, en gaat voor in browsers die hem ondersteunen. Stuur ze allebei: frame-ancestors voor huidige browsers, X-Frame-Options voor oudere. De oude waarde ALLOW-FROM wordt niet meer ondersteund, dus gebruik frame-ancestors voor alles buiten je eigen origin.

Referrer-Policy

Als een bezoeker een link volgt, vertelt de browser de volgende site in de Referer-header waar die vandaan kwam. Zonder beperking kan dat het volledige adres zijn, inclusief een zoekopdracht, een resettoken of een ordernummer in de URL. strict-origin-when-cross-origin stuurt binnen je eigen site het volledige adres, naar andere sites alleen het domein en van HTTPS naar HTTP niets. Huidige browsers doen dat al als de header ontbreekt, maar door hem te sturen maak je de keuze expliciet. Gebruik no-referrer op pagina’s met privégegevens in de URL, en vermijd unsafe-url, dat alles naar overal stuurt.

Permissions-Policy

Permissions-Policy, voorheen Feature-Policy, zet browserfuncties uit die je site niet gebruikt, voor de pagina en voor elk frame dat erin is ingesloten. Een lege lijst, (), betekent dat niemand de functie mag gebruiken:

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

Laat de functies die je wél gebruikt weg, of sta ze toe voor je eigen origin met (self), bijvoorbeeld payment=(self) in een webshop met een betaalknop.

Headers die je kunt weglaten

X-XSS-Protection stuurde een filter aan dat huidige browsers hebben verwijderd. Laat de header weg of stuur 0.

Expect-CT en Public-Key-Pins zijn verouderd. Pinning kon bezoekers buitensluiten van een site, en daarom hebben browsers het geschrapt.

Server en X-Powered-By zijn geen securityheaders, maar exacte versienummers daarin helpen aanvallers een exploit te kiezen. Kort ze in waar je server dat toelaat.

Waar je ze instelt

Stel de headers op één plek in, zo dicht mogelijk bij de rand: de webserver, het CDN of de middleware van de applicatie. In nginx en Apache:

# nginx (in the server block)
add_header Strict-Transport-Security “max-age=31536000” 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;

# Apache (.htaccess or virtual host, with mod_headers)
Header always set X-Content-Type-Options “nosniff”
Header always set Referrer-Policy “strict-origin-when-cross-origin”

Twee nginx-details zetten mensen vaak op het verkeerde been. Zonder always worden de headers alleen aan succesvolle responses toegevoegd, niet aan foutpagina’s. En een location-blok met ook maar één eigen add_header laat alle headers uit het serverblok vallen, dus één cacheheader voor afbeeldingen kan daar ongemerkt je securityheaders weghalen. Let ook op dezelfde header die twee keer wordt gezet, door de server en door de applicatie of het CDN: browsers gaan per header anders om met dubbele waarden, dus kies voor elke header één eigenaar.

Zo controleer je je headers

In een terminal toont curl -sI https://example.com de responseheaders. Controleer ook het uiteindelijke adres na redirects, een foutpagina en een statisch bestand, want die kunnen elk door een ander deel van je stack worden geserveerd. In een browser toont het tabblad Netwerk van de ontwikkelaarstools de headers van elk verzoek, en de console toont CSP-overtredingen.

Of gebruik onze gratis HTTP-headerchecker. Die toont elke header die een URL stuurt nadat redirects zijn gevolgd, geeft aan welke van de zes securityheaders ontbreken, markeert een paar waarden die ze tenietdoen, zoals een korte HSTS-max-age, een CSP die alleen Report-Only is, of unsafe-url, en laat zien hoe de pagina wordt gecachet en waar de tijd naartoe gaat. De tool controleert of de headers er zijn; hij beoordeelt niet wat een Content Security Policy toestaat, dus dat controleer je zelf.

Zorg dat ze niet verdwijnen

Securityheaders gaan zelden met veel kabaal kapot. Ze verdwijnen als een site naar een nieuwe server verhuist, er van CDN wordt gewisseld, een plugin wordt bijgewerkt of iemand een location-blok toevoegt, en de site blijft gewoon werken, dus niemand merkt het. Een HTTP-monitor in de uptime monitoring van Uptimecheck kan bij elk verzoek de responseheaders controleren, met tot 10 responsechecks per monitor, bijvoorbeeld:

Strict-Transport-Security is aanwezig.

X-Content-Type-Options is gelijk aan nosniff.

Content-Security-Policy bevat frame-ancestors.

Elke check moet slagen om de monitor als up te laten tellen. Een ontbrekende header zet hem dus op down, een tweede check bevestigt dat en je krijgt een melding. Zet de headerchecks op een eigen monitor als je niet wilt dat ze meetellen voor de uptime van de pagina. Checks draaien zo vaak als elke minuut vanuit Stockholm, Londen en New York; lees hoe je een API-endpoint monitort voor het instellen van responsechecks, en de checklist voor websiteonderhoud voor de rest van de routine.

Weet het op de dag dat een header verdwijnt. Probeer Uptimecheck 14 dagen gratis, geen creditcard nodig.

Vragen

Welke HTTP-securityheaders moet elke website sturen?

Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options of een CSP-directive frame-ancestors, Referrer-Policy en Permissions-Policy zijn eenvoudig toe te voegen. Content-Security-Policy biedt de meeste bescherming, maar moet eerst in Report-Only-modus worden getest.

Wat is Content-Security-Policy-Report-Only?

Dezelfde policy als Content-Security-Policy, maar de browser meldt alleen wat hij zou hebben geblokkeerd, in plaats van het te blokkeren. Gebruik het om de bronnen te vinden die je site echt nodig heeft voordat je de policy afdwingt.

Heb ik X-Frame-Options nog nodig als ik frame-ancestors gebruik?

Huidige browsers volgen frame-ancestors en negeren X-Frame-Options als beide aanwezig zijn. Allebei sturen kost niets en beschermt bezoekers met oudere browsers.

Is X-XSS-Protection nog nodig?

Nee. Het filter dat de header aanstuurde, is uit huidige browsers verwijderd. Laat de header weg of stuur 0, en vertrouw in plaats daarvan op een Content Security Policy.

Lees verder

Downtime is verleden tijd.

Stel je eerste monitor in binnen een minuut, of kijk eerst rond in de live demo.

14 dagen gratis proberen · Daarna gratis met 5 monitors · Geen creditcard