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.