HTTPS, certificaten en beveiligingsheaders
Headers die een browser instrueren
De verbinding is in orde: de client weet wie het andere eind vasthoudt. Kijk nu naar wat erover binnenkomt. De pagina van je service bevat tekst die uit verzoeken kwam, zoals boektitels. In hoofdstuk 7 zette je één aanroep van html.escape tussen een titel en de pagina. Als er ooit één
Eén verdedigingslinie is niet veel voor een fout die zo makkelijk gemaakt wordt. Een tweede linie moet in de browser leven, want daar zou het script draaien. De server kan erom vragen met een header in het antwoord.
Een policy is een lijst met bronnen
De header is Content-Security-Policy. Zijn waarde is een policy: een
content-security-policy:
default-src 'self'; form-action 'self'; frame-ancestors 'none'
Splits hem zoals een browser dat doet. Voer dit uit:
De eerste regel is default-src allows ["'self'"]. default-src is de directive voor alles wat een pagina laadt: scripts, afbeeldingen, stylesheets. 'self', met de aanhalingstekens, betekent de eigen default-src 'self' zegt dus dat deze pagina dingen mag laden van haar eigen origin en van nergens anders.
Een policy verandert niets op de server en niets aan de verbinding. De server stuurt de pagina zoals die is. De header is een instructie, en alleen een browser voert die uit.
wire.py haalt de pagina en haar policy-header op. Wat verandert de policy?
Wat default-src ‘self’ tegenhoudt
Stel dat het escapen één keer gemist wordt. Dit zijn de twee versies van een titel binnen zijn <span class="title"> in de pagina, geëscapet en niet:
<script src="https://other.example/steal.js"></script>
<script src="https://other.example/steal.js"></script>
De tweede regel is een script-element. Onder default-src 'self' weigert een browser die de standaard volgt het te laden, omdat https://other.example niet de origin van de pagina is.
Een aanvaller heeft echter geen andere site nodig. Het script kan in de pagina zelf staan, tussen de tags, of in een attribuut zoals onclick. Dat zijn inline scripts. Een inline script wordt uit geen enkel bestand geladen, dus 'self' dekt het niet. Een policy staat inline scripts alleen toe als ze dat zegt, met de bron 'unsafe-inline'. De naam is een waarschuwing. Jouw policy bevat hem niet, dus een browser draait geen inline script en geen inline event handler op je pagina.
Hier is een model van die beslissing. Het dekt twee regels van een lange standaard: waar een scriptbestand vandaan komt, en of een script inline is. Voer het uit:
De eerste twee regels zijn https://reading.example/app.js True en https://other.example/steal.js False. Deze policy werkt voor je pagina omdat page.html geen eigen script en geen eigen styles heeft. Een grotere pagina die inline scripts nodig heeft, noemt elk ervan in haar policy met een nonce of een hash, en een nieuwe policy kun je eerst in report-only-modus uitproberen. Dat zijn namen om te herkennen.
Een gemiste html.escape laat <script>alert(1)</script> in de pagina, met de code tussen de tags. Het antwoord zegt default-src 'self'. Wat doet een browser ermee?
Wat drukt de laatste regel van het tweede blok af?
Wat het niet dekt
default-src is een terugvaloptie voor de directives over laden. Twee van de dingen die een pagina kan doen, zijn geen laden, en die vallen er niet op terug.
Het eerste is waar een formulier op de pagina zijn velden naartoe mag sturen. Geïnjecteerde markup zou het doel van het formulier naar een andere site kunnen veranderen. form-action 'self' houdt elk formulier van de pagina gericht op haar eigen origin.
Het tweede is wie de pagina binnen een frame mag tonen. Een andere site kan je pagina in een onzichtbaar frame zetten en een lezer verleiden erop te klikken; dat heet clickjacking. frame-ancestors 'none' vertelt de browser dat geen enkele pagina deze in een frame mag zetten. Een oudere header, X-Frame-Options, vraagt hetzelfde.
Welke hiervan laat default-src 'self' open?
Een tweede slot, geen vervanging
De policy maakt escapen niet overbodig. Zonder html.escape kan een titel nog steeds markup toevoegen: een kop, een link, een formulier dat om een wachtwoord vraagt. Er draait geen script, en de pagina toont toch wat een aanvaller schreef. Escapen houdt de pagina correct. De policy beperkt de schade als het escapen één keer gemist wordt. En de policy helpt alleen in een browser die haar toepast.
Is html.escape in render_page() met deze policy nog nodig?
Je kunt de policy nu lezen. Nog geen antwoord van je service draagt hem. De volgende les voegt hem toe, met nog drie headers, in één