HTTP security headers

17 apr. 2016
Tags: Webside

Nettlesere har de siste årene fått mye spennende teknologi for å gjøre hverdagen på nett enda tryggere. I samme stil som SSL Labs med deres test for oppsett av sikker HTTP (TSL/SSL), har vi nå securityheaders.io. Mål nummer én var å lære mer om disse standardene, og mål to selvsagt å få en A+ selv. HTTP-headere (sikkerhetshoder) sendes etter at autentisering og kryptering er startet ved TSL/SSL, men før HTML og innhold blir overført mellom server og klient. Og alt dette påvirker bare klienter, dersom "hodene" støttes.


Innledningsvis skal det sies at det er mulig å få en høy score på denne testen, uten at sikkerhetsnivået nødvendigvis blir noe bedre, nettopp fordi det er mange måter å konfigurere dem opp på. Det er nå 6 hoder som sjekkes:

x-xss-protection, X-Content-Type-Options og X-Frame-Options er alle veldig trivielle. Den første aktiverer beskyttelse mot "cross site scripting" i nettlesere som støtter dette. Anbefalt er "X-XSS-Protection: 1; mode=block". Den andre skrur av gjetting av "filtype" der serveren eksplisitt deklarere såkalt MIME-type. Settes til "nosniff". Den tredje brukes for å begrense muligheten for at siden kan bli "framed" ("vist inni et vindu på en annen side"). "SAMEORIGIN" anbefales for denne.

strict-transport-security forteller nettleseren at, fra og med nå, skal du ikke be om noe fra mitt domene uten at det går kryptert over HTTPS. Det er selvsagt viktig å ikke sende denne headeren dersom deler av nettstedet fremdeles serveres ukryptert. Man konfigurerer "max-age" til antall sekunder dette skal gjelde. Denne verdien oppdateres/"nullstilles" hver gang nettleseren henter ned en ny side fra nettstedet.

Content-Security-Policy og Public-Key-Pins krever begge en del omtanke før de settes i drift. Den første er ganske interessant. Den er en slags "brannmur" for hvilke ressurser (javascript, bilder, video, css osv) som denne siden tillater seg selv å laste inn. Slike ressurser kan både lastes inn via filer, og de kan være "inline", dvs. lagt direkte inn i HTML-siden. Og siden inline kode typisk er det innbrytere prøver å lure inn på populære nettområder, er innline script og css som standard deaktivert. Her er det altså store muligheter for å knekke masse funksjonalitet om headeren blir implementert for strengt.

Jeg valgte en variant slik som dette:

default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{{csrf-token}}'

Jeg tillater i utgangspunktet bare ressurser fra eget domene. For css måtte jeg tillate inline (via "unsafe-inline") grunnet at Djangorammeverket bruker inline når den lager skjema. For javascript (script-src) har jeg noen få inline script-snutter her og der i forbindelse med noen grafer. En rask fiks var derfor å bruke "nonce" (et tilfeldig nummer) satt til djangos csrf-token. Den er dermed unik per besøkende, og jeg slipper å måtte bruke en "unsafe-inline" generelt. Et greit kompromiss slik jeg ser det. Se blant annet html5rocks.com for mere detaljer.

Public-Key-Pins ber nettleseren om å huske et sett med sertifikater, som den så skal bruke for å nekte å koble til dersom klienten ikke gjenkjenner noen av sertifikatene ved en senere anledning. Alle sertifikatutstedere kan lage sertifikater for alle domener, og dette tiltaket lager en binding mellom det som faktisk er autentisk og domenet, slik at eventuelle "falske" (men gyldige) sertifikat nektes. Denne headeren kan være litt "skummel", siden det er fort gjort å "låse seg selv ute" dersom man endrer sertifikatutsteder eller privat nøkkel. Det er derfor krav om minst én reservenøkkel. Som nevnt må bare ét av sertifikatene matche et gyldig sertifikat, og man kan velge å "pinne" hvor man vil i sertifikatkjeden. Man kan altså pinne mot et root-sertifikat (sertifikatutstedere), en intermediate CA, eller man kan pinne mot eget sertifikat (mer kontroll, større risiko). Man kan selvsagt legge til nye reserver underveis, så lenge forbindelsen blir godkjent. En pinning/(reserve)nøkkel er en hash-verdi basert på den offentlige nøkkelen, som både er i sertifikat og i CSR (certificate signing request). Så lenge den ikke endres, f.eks. ved resignering av CA, vil hash-verdien være lik. En gyldighetsperiode brukes for å instruere nettleseren hvor lenge bindingen skal gjelde, og denne oppdateres også for hver gang nettleseren mottar en gyldig oppkobling mot domenet.

Jeg fant masse gode tips på scotthelme.co.uk og timtaubert.de.

Oppdatering Det viser seg at man må bruke dobbelquote (") rundt sha256-verdiene i public-key-pinning. Jeg hadde brukt enkeltquote (') og fikk derfor failed på SSLlabs test. Fikset det, og gikk opp til A+. Det er viktig å ha minst én reservenøkkel, samt en max-age større enn 0. Jeg kom også over en ganske artig artikkel om feil bruk av key-pinning.

Public-Key-Pins:pin-sha256="ntaa4dzdzEmjCVMrlFOJTlfUQJKu3LoMqi1qlyCjoek="; pin-sha256="orne4NMB+gRZ5FIMhpzlzLZQ/M6Xk6ml0Fd/c6qp2lQ="; pin-sha256="181NMJKq1c28dYCENy2lsu33dkifRayer4wlWhYAoRg="; max-age=5184000;