De auteur heeft Girls Who Code geselecteerd om een donatie te ontvangen als onderdeel van het Write for Donations-programma.
Introductie
Wanneer je een website bezoekt, worden verschillende bronnen gebruikt om deze te laden en weer te geven. Als voorbeeld, wanneer je naar https://www.digitalocean.com
gaat, downloadt je browser de HTML en CSS rechtstreeks van digitalocean.com
. Afbeeldingen en andere middelen worden echter gedownload van assets.digitalocean.com
, en analyse-scripts worden geladen vanaf hun respectievelijke domeinen.
Sommige websites maken gebruik van een veelvoud aan verschillende services, stijlen en scripts om hun inhoud te laden en weer te geven, en je browser zal dit allemaal uitvoeren. Een browser weet niet of code kwaadaardig is, dus het is de verantwoordelijkheid van de ontwikkelaar om gebruikers te beschermen. Omdat er veel bronnen op een website kunnen zijn, is het hebben van een functie in de browser die alleen goedgekeurde bronnen toestaat een goede manier om te zorgen dat gebruikers niet worden gecompromitteerd. Hiervoor zijn Content Security Policies (CSP’s) bedoeld.
Door een CSP-header te gebruiken, kan een ontwikkelaar expliciet bepaalde bronnen toestaan om uit te voeren, terwijl alle andere worden voorkomen. Omdat de meeste sites wel honderden bronnen kunnen hebben, en elke bron moet worden goedgekeurd voor de specifieke categorie van bron die het is, kan het implementeren van een CSP een tijdrovende taak zijn. Een website met een CSP zal echter veiliger zijn, omdat hiermee alleen goedgekeurde bronnen worden toegestaan om uit te voeren.
In deze tutorial zul je een CSP implementeren in een basis Django-toepassing. Je zult de CSP aanpassen om bepaalde domeinen en inline bronnen toe te staan om uit te voeren. Optioneel kun je ook Sentry gebruiken om overtredingen te registreren.
Vereisten
Om deze tutorial te voltooien, heb je het volgende nodig:
- A working Django project (version 3 or greater is preferred), either on your local machine or a DigitalOcean Droplet. If you don’t have one, you can create one with the tutorial, How to Install Django and Set Up a Development Environment on Ubuntu 20.04.
- A web browser like Firefox or Chrome and an understanding of browser network tools. For more on using browser network tools, check out the product documentation for the Network Monitor in Firefox or the DevTools Network Tab in Chrome. For more general guidance on browser developer tools, see the guide: What are Browser Developer Tools?
- Kennis van Python 3 en Django, die je kunt opdoen uit de tutoriaalreeks Hoe te coderen in Python en Django-ontwikkeling.
- Een account op Sentry voor het volgen van CSP-overtredingen (optioneel).
Stap 1 — Het maken van een demo-weergave
In deze stap zul je wijzigen hoe jouw toepassing weergaven verwerkt, zodat je CSP-ondersteuning kunt toevoegen.
Als voorwaarde heb je Django geïnstalleerd en een voorbeeldproject ingesteld. De standaardweergave in Django is te eenvoudig om alle mogelijkheden van de CSP-middleware te demonstreren, dus je zult een eenvoudige HTML-pagina maken voor deze tutorial.
Navigeer naar de projectmap die je hebt aangemaakt in de vereisten:
Terwijl je je binnen de django-apps
directory bevindt, maak je je virtuele omgeving aan. We noemen het generiek env
, maar je zou een naam moeten gebruiken die betekenisvol is voor jou en je project.
Nu, activeer de virtuele omgeving met het volgende commando:
Binnen de virtuele omgeving maak je een views.py
bestand aan in je projectmap met behulp van nano
, of je favoriete teksteditor:
Voeg nu een basisweergave toe die een index.html
sjabloon zal renderen die je vervolgens zult maken. Voeg het volgende toe aan views.py
:
Sla het bestand op en sluit het af wanneer je klaar bent.
Maak een index.html
sjabloon aan in een nieuwe templates
directory:
Voeg het volgende toe aan index.html
:
De weergave die we hebben gemaakt zal deze eenvoudige HTML-pagina renderen. Het zal de tekst Hallo, Sammy! tonen samen met een afbeelding van Sammy de Haai.
Sla het bestand op en sluit het af wanneer je klaar bent.
Om toegang te krijgen tot deze weergave, moet je urls.py
bijwerken:
Importeer het views.py
bestand en voeg een nieuwe route toe door de gemarkeerde regels toe te voegen:
De nieuwe weergave die je zojuist hebt gemaakt, zal nu zichtbaar zijn wanneer je /
bezoekt (wanneer de applicatie wordt uitgevoerd).
Sla het bestand op en sluit het af.
Tenslotte moet je INSTALLED_APPS
bijwerken om testsite
op te nemen in settings.py
:
Hier voeg je testsite
toe aan de lijst met applicaties in settings.py
zodat Django enkele aannames kan maken over de structuur van je project. In dit geval zal het aannemen dat de map templates
Django-templates bevat die je kunt gebruiken voor het renderen van weergaven.
Vanuit de hoofdmap van het project (testsite
), start je de Django-ontwikkelingsserver met het volgende commando, waarbij je je-server-ip
vervangt door het IP-adres van je eigen server.
Open een browser en ga naar je-server-ip:8000
. De pagina zou er ongeveer zo uit moeten zien:
Op dit punt wordt er een profielfoto van Sammy de Haai weergegeven op de pagina. Onder de afbeelding staat de tekst Hallo, Sammy! in blauw script.
Om de Django-ontwikkelingsserver te stoppen, druk je op CONTROL-C
.
In deze stap heb je een eenvoudige weergave gemaakt die fungeert als de startpagina van je Django-project. Hierna voeg je CSP-ondersteuning toe aan je applicatie.
Stap 2 — CSP Middleware installeren
In deze stap installeer je een CSP-middleware zodat je CSP-headers kunt toevoegen en kunt werken met CSP-functies in je weergaven. Middleware voegt extra functionaliteit toe aan elke request of response die Django verwerkt. In dit geval voegt de Django-CSP Middleware CSP-ondersteuning toe aan Django-responses.
Eerst installeer je Mozilla’s CSP Middleware in je Django-project met behulp van pip
, Python’s pakketbeheerder. Gebruik het volgende commando om het benodigde pakket te installeren van PyPi, de Python Package Index. Om het commando uit te voeren, kun je ofwel de Django-ontwikkelingsserver stoppen met CONTROL-C
of een nieuwe tabblad openen in je terminal:
Voeg vervolgens de middleware toe aan de instellingen van je Django-project. Open settings.py
:
Met django-csp
geïnstalleerd, kun je nu de middleware toevoegen in settings.py
. Dit voegt CSP-headers toe aan je antwoorden.
Voeg de volgende regel toe aan de configuratie-array MIDDLEWARE
:
Sla het bestand op en sluit het af wanneer je klaar bent. Je Django-project ondersteunt nu CSP’s. In de volgende stap begin je met het toevoegen van CSP-headers.
Stap 3 — Implementatie van een CSP-header
Nu je project CSP’s ondersteunt, is het klaar om te worden beveiligd. Om dat te bereiken, configureer je het project om CSP-headers toe te voegen aan je antwoorden. Een CSP-header vertelt de browser hoe deze zich moet gedragen wanneer het een bepaald type inhoud tegenkomt. Dus als de header zegt dat alleen afbeeldingen van een bepaald domein zijn toegestaan, zal de browser alleen afbeeldingen van dat domein toestaan.
Gebruik nano of je favoriete teksteditor om settings.py
te openen:
Definieer de volgende variabelen ergens in het bestand:
Deze regels zijn de standaardinstellingen voor uw CSP. Deze regels geven aan welke bronnen zijn toegestaan voor afbeeldingen, stylesheets en scripts, respectievelijk. Op dit moment bevatten ze allemaal de string 'self'
, wat betekent dat alleen bronnen van uw eigen domein zijn toegestaan.
Sla het bestand op en sluit het af wanneer u klaar bent.
Voer uw Django-project uit met het volgende commando:
Wanneer u uw-server-ip:8000
bezoekt, ziet u dat de site kapot is:
Zoals verwacht, verschijnt de afbeelding niet en verschijnt de tekst in standaardstijl (vet zwart). Dit betekent dat de CSP-header wordt afgedwongen en dat onze pagina nu veiliger is. Omdat de weergave die u eerder heeft gemaakt, verwijst naar stylesheets en afbeeldingen van domeinen die niet van u zijn, blokkeert de browser ze.
Uw project heeft nu een werkende CSP die de browser vertelt om bronnen te blokkeren die niet van uw domein zijn. Hierna zult u de CSP aanpassen om specifieke bronnen toe te staan, wat het ontbreken van afbeeldingen en stijlen op de startpagina zal oplossen.
Stap 4 — Aanpassen van de CSP om Externe Bronnen toe te staan
Nu je een basis CSP hebt, ga je deze aanpassen op basis van wat je op je site gebruikt. Als voorbeeld, een website die Adobe Fonts en ingesloten YouTube-video’s gebruikt, moet deze bronnen toestaan. Als uw website echter alleen afbeeldingen weergeeft vanaf uw eigen domein, kunt u de instellingen voor afbeeldingen op hun restrictieve standaardwaarden laten staan.
De eerste stap is om elke bron te vinden die je moet goedkeuren. U kunt hiervoor de ontwikkelaarstools van uw browser gebruiken. Open de Netwerkbewaking in Inspect Element, vernieuw de pagina en bekijk de geblokkeerde bronnen:
In het Netwerklogboek wordt weergegeven dat twee bronnen worden geblokkeerd door de CSP: een stylesheet van fonts.googleapis.com en een afbeelding van html.sammy-codes.com. Om deze bronnen toe te staan in de CSP-header, moet u de variabelen in settings.py aanpassen.
Om bronnen van externe domeinen toe te staan, voegt u het domein toe aan het gedeelte van de CSP dat overeenkomt met het bestandstype. Dus, om een afbeelding van html.sammy-codes.com toe te staan, voegt u html.sammy-codes.com toe aan CSP_STYLE_SRC.
Open settings.py en voeg het volgende toe aan de variabele CSP_STYLE_SRC:
Nu, in plaats van alleen afbeeldingen van uw domein toe te staan, staat de site ook afbeeldingen toe van html.sammy-codes.com.
De indexweergave gebruikt Google-lettertypen. Google levert uw site de lettertypen (vanaf https://fonts.gstatic.com
) en een stylesheet om ze toe te passen (vanaf https://fonts.googleapis.com
). Voeg om de lettertypen te laden het volgende toe aan uw CSP:
Vergelijkbaar met het toestaan van afbeeldingen van html.sammy-codes.com
, staat u ook stylesheets toe van fonts.googleapis.com
en lettertypen van fonts.gstatic.com
toe. Ter referentie, de stylesheet geladen vanaf fonts.googleapis.com
wordt gebruikt om de lettertypen toe te passen. De lettertypen zelf worden geladen vanaf fonts.gstatic.com
.
Sla het bestand op en sluit het.
Waarschuwing: Net als bij self
, zijn er andere trefwoorden zoals unsafe-inline
, unsafe-eval
, of unsafe-hashes
die kunnen worden gebruikt in een CSP. Het wordt sterk aanbevolen om deze regels te vermijden in uw CSP. Hoewel deze de implementatie gemakkelijker maken, kunnen ze worden gebruikt om de CSP te omzeilen en deze nutteloos te maken.
Zie voor meer informatie de Mozilla-productdocumentatie voor “Onveilig inline script”.
Nu zullen Google-lettertypen worden toegestaan om stijlen en lettertypen op uw site te laden en html.sammy-codes.com
zal worden toegestaan om afbeeldingen te laden. Wanneer u echter een pagina op uw server bezoekt, zult u merken dat alleen afbeeldingen nu worden geladen. Dat komt doordat de inline stijlen in de HTML die worden gebruikt om de lettertypen toe te passen, niet zijn toegestaan. Dat zult u in de volgende stap oplossen.
Stap 5 — Werken met Inline Scripts en Stijlen
Op dit punt hebt u de CSP aangepast om externe bronnen toe te staan. Maar inline bronnen, zoals stijlen en scripts in uw weergave, zijn nog steeds niet toegestaan. In deze stap zult u ervoor zorgen dat deze werken, zodat u lettertype-styling kunt toepassen.
Er zijn twee manieren om inline scripts en stijlen toe te staan: nonces en hashes. Als u merkt dat u inline scripts en stijlen vaak aanpast, gebruik dan nonces om frequente wijzigingen in uw CSP te vermijden. Als u zelden inline scripts en stijlen bijwerkt, is het gebruik van hashes een redelijke aanpak.
Het gebruik van nonce
om Inline Scripts toe te staan
Eerst zult u de nonce-benadering gebruiken. Een nonce is een willekeurig gegenereerde token die uniek is voor elk verzoek. Als twee personen uw site bezoeken, krijgen ze elk een unieke nonce
die is ingebed in de inline scripts en stijlen die u goedkeurt. Denk aan nonce als een eenmalig wachtwoord dat bepaalde delen van een site goedkeurt om te worden uitgevoerd voor een enkele sessie.
Om nonce-ondersteuning aan uw project toe te voegen, zult u uw CSP bijwerken in settings.py
. Open het bestand om te bewerken:
Voeg script-src
toe aan CSP_INCLUDE_NONCE_IN
in het settings.py
bestand.
Definieer CSP_INCLUDE_NONCE_IN
ergens in het bestand en voeg 'script-src'
eraan toe:
CSP_INCLUDE_NONCE_IN
geeft aan aan welke inline-scripts je nonce
-attributen mag toevoegen. CSP_INCLUDE_NONCE_IN
wordt behandeld als een array omdat meerdere gegevensbronnen nonces ondersteunen (bijvoorbeeld style-src
).
Sla het bestand op en sluit het.
Nonces mogen nu worden gegenereerd voor inline-scripts wanneer je het nonce
-attribuut aan hen toevoegt in je weergavetemplate. Probeer dit uit met een eenvoudig JavaScript-fragment.
Open index.html
om te bewerken:
Voeg het volgende fragment toe in de <head>
van de HTML:
Dit fragment print Hello from the console!"
naar de browserconsole. Omdat je project een CSP heeft die alleen inline-scripts toestaat als ze een nonce
hebben, zal dit script niet worden uitgevoerd en in plaats daarvan een foutmelding produceren.
Je kunt deze foutmelding zien in de console van je browser wanneer je de pagina vernieuwt:
De afbeelding wordt geladen omdat je externe bronnen hebt toegestaan in de vorige stap. Zoals verwacht, is de styling momenteel standaard omdat je nog geen inline-stijlen hebt toegestaan. Ook zoals verwacht, is het consolebericht niet afgedrukt en wordt er een foutmelding weergegeven. Je zult het een nonce
moeten geven om het goed te keuren.
Je kunt dat doen door nonce="{{request.csp_nonce}}"
toe te voegen aan dit script als attribuut. Open index.html
voor bewerking en voeg het gemarkeerde gedeelte toe zoals hier getoond:
Sla je bestand op en sluit het af wanneer je klaar bent.
Als je de pagina vernieuwt, zal het script nu worden uitgevoerd:
Wanneer je in Element inspecteren kijkt, zul je merken dat er geen waarde is voor het attribuut:
De waarde verschijnt niet om veiligheidsredenen. De browser heeft de waarde al verwerkt. Het is verborgen zodat eventuele scripts met toegang tot de DOM er geen toegang toe kunnen krijgen en het op een ander script kunnen toepassen. Als je in plaats daarvan Pagina bron bekijken, dit is wat de browser heeft ontvangen:
Merk op dat elke keer dat je de pagina vernieuwt, de nonce
-waarde verandert. Dit komt doordat de CSP-middleware in ons project een nieuwe nonce
genereert voor elke aanvraag.
Deze nonce
-waarden worden toegevoegd aan de CSP-header wanneer de browser de reactie ontvangt:
Elk verzoek dat de browser naar je site maakt, zal een unieke nonce
-waarde hebben voor dat script. Aangezien de nonce
wordt verstrekt in de CSP-header, betekent dit dat de Django-server dat specifieke script heeft goedgekeurd om uit te voeren.
Je hebt je project bijgewerkt om te werken met nonce, die kan worden toegepast op meerdere bronnen. Bijvoorbeeld, je kunt het ook toepassen op stijlen door CSP_INCLUDE_NONCE_IN
bij te werken om style-src
toe te staan. Maar er is een eenvoudigere aanpak om inline bronnen goed te keuren, en dat is wat je hierna zult doen.
Het Gebruik van Hashes om Inline Stijlen Toe te Staann
Een andere benadering om inline scripts en stijlen toe te staan is met hashes. Een hash is een unieke identificator voor een bepaalde inline bron.
Als voorbeeld, dit is de inline stijl in ons sjabloon:
Op dit moment werken de stijlen echter niet. Wanneer je de site bekijkt in de browser, worden de afbeeldingen succesvol geladen, maar de lettertypen en stijlen worden niet toegepast:
In de console van de browser vind je een foutmelding dat een inline stijl de CSP schendt. (Er kunnen andere fouten zijn, maar zoek naar de fout over inline stijl.)
De fout wordt geproduceerd omdat de stijl niet is goedgekeurd door onze CSP. Maar, let op dat de foutmelding de hash levert die nodig is om de stijlfragment goed te keuren. Deze hash is uniek voor dit specifieke stijlfragment. Geen enkele andere fragmenten zullen ooit dezelfde hash hebben. Wanneer deze hash wordt geplaatst binnen de CSP, wordt telkens wanneer deze specifieke stijl wordt geladen, deze goedgekeurd. Maar als je deze stijlen ooit wijzigt, moet je de nieuwe hash krijgen en de oude vervangen door deze in de CSP.
Je gaat de hash nu toepassen door deze toe te voegen aan CSP_STYLE_SRC
in settings.py
, als volgt:
Het toevoegen van de sha256-...
hash aan de CSP_STYLE_SRC
lijst zal de browser in staat stellen om de stylesheet zonder fouten te laden.
Sla het bestand op en sluit het.
Nu, herlaad de site in de browser, en de lettertypen en stijlen moeten succesvol laden:
Inline stijlen en scripts functioneren nu correct. In deze stap heb je twee verschillende benaderingen gebruikt, nonces en hashes, om inline stijlen en scripts toe te staan.
Maar, er is een belangrijk probleem om aan te pakken. CSP’s zijn tijdrovend om te onderhouden, vooral voor grote websites. Je hebt mogelijk een manier nodig om bij te houden wanneer de CSP een bron blokkeert, zodat je kunt bepalen of het een kwaadaardige bron is of gewoon een kapot onderdeel van je site. In de volgende stap zul je Sentry gebruiken om alle schendingen geproduceerd door je CSP te registreren en bij te houden.
Stap 6 — Schendingen rapporteren met Sentry (Optioneel)
Aangezien CSP’s doorgaans strikt zijn, is het goed om te weten wanneer het inhoud blokkeert — vooral omdat het blokkeren van inhoud waarschijnlijk betekent dat sommige functionaliteiten op je site niet zullen werken. Tools zoals Sentry kunnen je laten weten wanneer de CSP verzoeken van gebruikers blokkeert. In deze stap configureer je Sentry om CSP-schendingen te loggen en rapporteren.
Als voorwaarde heb je je aangemeld voor een account bij Sentry. Nu zul je een project aanmaken.
In de linkerbovenhoek van het Sentry-dashboard, klik op het Projects tabblad:
In de rechterbovenhoek, klik op de Create Project knop:
U zult een aantal logo’s zien met een titel die zegt Kies een platform. Kies Django:
Vervolgens, onderaan, geef uw project een naam (voor dit voorbeeld zullen we sammys-tutorial
gebruiken), en klik op de Project maken knop:
Sentry zal u een codefragment geven om toe te voegen aan uw settings.py
bestand. Bewaar dit fragment om later toe te voegen.
In uw terminal, installeer de Sentry SDK:
Open settings.py
als volgt:
Voeg het volgende toe aan het einde van het bestand, en zorg ervoor dat u SENTRY_DSN
vervangt door de waarde van het dashboard:
Deze code wordt door Sentry geleverd zodat het eventuele fouten kan registreren die zich voordoen in uw applicatie. Het is de standaardconfiguratie voor Sentry en initialiseert Sentry voor het loggen van problemen op onze server. Technisch gezien hoeft u Sentry niet te initialiseren op uw server voor CSP-overtredingen, maar in het zeldzame geval dat er een probleem is met het renderen van nonces of hashes, worden deze fouten geregistreerd bij Sentry.
Sla het bestand op en sluit het.
Ga vervolgens terug naar het dashboard van je project en klik op het tandwielicoon om naar Instellingen te gaan:
Ga naar het tabblad Beveiligingsheaders:
Kopieer de report-uri
:
Voeg het toe aan je CSP als volgt:
Zorg ervoor dat je your-report-uri
vervangt door de waarde die je hebt gekopieerd van het dashboard.
Sla je bestand op en sluit het. Wanneer er nu een overtreding van het CSP-beleid optreedt, zal Sentry deze naar deze URI loggen. Je kunt dit proberen door een domein of hash uit je CSP te verwijderen, of door de nonce
uit het script te verwijderen dat je eerder hebt toegevoegd. Laad de pagina in de browser en je zult de fout zien in de Problemen-pagina van Sentry:
Als je merkt dat je overweldigd wordt door het aantal logs, kun je ook
CSP_REPORT_PERCENTAGE
in settings.py
definiëren om slechts een percentage van de logs naar Sentry te sturen.
Nu krijg je altijd een melding wanneer er een overtreding van het CSP-beleid is en kun je de fout bekijken in Sentry.
Conclusie
In dit artikel heb je je Django-toepassing beveiligd met een inhoudsbeveiligingsbeleid. Je hebt je beleid bijgewerkt om externe bronnen toe te staan en gebruikt nonces en hashes om inline scripts en stijlen toe te staan. Je hebt het ook geconfigureerd om schendingen naar Sentry te sturen. Als volgende stap, bekijk de Django CSP-documentatie om meer te leren over hoe je je CSP kunt afdwingen.