Selainturvallisuus

Content Security Policy (CSP)

Keskitaso
1 h 30 min

Mikä on CSP?

Content Security Policy (CSP, "sisällön suojauskäytäntö") on selaimen turvakontrolli, jonka verkkosivut voivat vapaaehtoisesti ottaa suojakseen lähettämällä Content-Security-Policy -otsakkeen HTTP-vastauksissaan.

CSP:n perus toimintaperiaate on lisätä verkkosivun turvallisuutta rajoittamalla mitä sivustolla saa tapahtua ja mistä sivustolle saa ladata resursseja, kuten skriptejä.

CSP on siis selainpuolen toteutus vähimmäisen oikeuden periaatteesta (principle of least privilege), eli annetaan selaimelle vain välttämättömät oikeudet, jolloin hyökkäyksen sattuessa hyökkääjällä on mahdollisimman rajatut oikeudet aiheuttaa vahinkoa.

Sivun alalaidassa on kymmenen labraa joissa pääset harjoittelemaan CSP:n käyttöä käytännössä erilaisissa tilanteissa!

Miltä CSP suojaa?

CSP on ennen kaikkea tehokas suojaus XSS-hyökkäyksiä vastaan, mutta tehokkaan suojan saadakseen, CSP pitää tehdä oikein.

Lisäksi CSP voi suojata muita XSS:n kaltaisia hyökkäyksiä vastaan, jossa yritetään ladata esimerkiksi tyylejä (CSS:ää), joka ei kuulu web-sovellukseen.

Lopulta CSP:llä on mahdollista suojata joitakin kehystyshyökkäyksiä (kuten clickjacking) vastaan rajaamalla, että mitkä (jos mitkään) ulkoiset sivustot saavat kehystää sivun (esim. IFRAME-elementillä).

XSS-suojaus on kuitenkin CSP:n pääasiallinen tarkoitus.

Mitä XSS (Cross-Site Scripting)-hyökkäykset sitten ovat?

XSS on yleistermi haavoittuvuuksille, jotka johtavat siihen, että hyökkääjä saa sovelluksen selaimen päässä suoritettavaan (JavaScript) -koodiin tehtyä haitallisia muutoksia. Haavoittuvuus on käsitelty laajemmin Hakatemian XSS-kurssilla, mutta tässä on yksinkertainen esimerkki.

Esimerkki : XSS-haavoittuvuus

Oletetaan, että sivustolla on tällainen hakutoiminnallisuus:

echo "<p>Search results for: " . $_GET('search') . "</p>"

Toiminnallisuus on haavoittuva XSS-hyökkäyksille koska sovellus rakentaa HTML:ää turvattomasti. Url-parametri search asetetaan suoraan osaksi HTML-merkkijonoa, jolloin pääsee muuttamaan HTML:n rakennetta.

Hyökkääjän on mahdollista hyödyntää haavoittuvuutta rakentamalla sovellukseen seuraavanlaisia linkkejä:

https://www.example.com/?search=<script>alert('XSS')</script>

Kun uhrin avaa linkin, sovellus rakentaa turvattomasti HTML-vastauksen joka lähetetään uhrin selaimeen seuraavanlaisena:

<p>
  Search results for:
  <script>
    alert('XSS')
  </script>
</p>

Koodin alert('XSS') sijasta hyökkääjä toki tekisi jotain muuta kuten varastaisi käyttäjän tietoja tai suorittaisi sovelluksessa ei-haluttuja toimintoja.

Miten CSP sitten käytännössä suojaa XSS-haavoittuvuuksia vastaan?

Otetaan käytännön esimerkkinä Hakatemian nettisivut. Ladataan sivu ja simuloidaan hyökkäys lisäämällä koodi <script>alert("XSS")</script> HTTP-vastaukseen käyttämällä BurpSuite -työkalua.

"XSS" alert-ikkunaa ei kuitenkaan ilmesty. Miksi? - Vastaus löytyy selaimen konsolista. Hakatemian CSP estää hyökkäyksen, koska se ei salli tämän kaltaisia inline-skriptejä.

Miten CSP toimii?

Käytännössä CSP toimii niin, että web-palvelin palauttaa HTTP-vastauksessaan (Content-Security-Policy -otsakkeessa) politiikan, joka määrittää esimerkiksi, että minkälaisia resursseja sivusto saa ladata tai suorittaa.

HTTP-vastaus
Content-Security-Policy: script-src 'self'; img-src https://images.google.com/

Selain sitten tulkitsee tätä säännöstöä aina, kun sivustollasi tapahtuu joku CSP:n tukema toiminto, ja joko sallii sen tai kieltää sen. Kieltämisestä ilmestyy punaista herjaa selaimen konsoliin sekä raportti CSP-raportinkäsittelijääsi, jos olet ottanut sellaisen käyttöön report-uri direktiivillä (tästä myöhemmin lisää).

Direktiivit

CSP koostuu direktiiveistä. Yllä olevassa esimerkissä on kaksi direktiiviä: Skriptien rajoitus (script-src) ja kuvien rajoitus (img-src).

Direktiivit on erotettu toisistaan puolipisteellä (;) ja sisältävät lähteitä, joista kyseistä resurssia voi ladata.

Kaikille resursseille, joita CSP voi rajoittaa on oma direktiivi.

Direktiivien käyttäytyminen

Jos direktiiviä ei ole asetettu ollenkaan, resurssia ei ole rajoitettu mitenkään. Tämä on oletustila kaikilla verkkosivuilla.

Jos direktiivi on asetettu, resurssi on lähtökohtaisesti kokonaan estetty ja pelkästään direktiivissä listatut lähteet on sallittuina.

Jos oletusdirektiivi (default-src, katsotaan tämä kohta) on asetettu, jotkut direktiivit saavat arvon siitä, jos ei niitä ole asetettu suoraan.

Tietyt direktiiveissä listatut lähteet syrjäyttävät toiset lähteet. Esimerkiksi 'hash-' ja 'nonce-' direktiivit syrjäyttävät 'unsafe-inline' avainsanan ja 'strict-dynamic' syrjäyttää sekä 'unsafe-inline' avainsanan, että URL-pohjaiset sallimiset. Palataan siihen, mitä nämä tarkoittavat hieman alempana.

Näin rajoitat JavaScript -koodin suoritusta sivulla

CSP:n eittämättä tärkein tehtävä on rajoittaa JavaScript -koodin suorittamista verkkosivustolla. Tarkoitus on sinänsä yksinkertainen: Sallitaan kaikki JavaScript-koodi, joka sivustolla on tarkoitus suorittaa ja estetään kaikki muu.

Toteutus voi olla joko helppo tai sitten se voi vaatia hieman kikkailua ja muutoksia suojattavaan sovellukseen.

CSP:n eri direktiivien ja niiden vaikutusten toisiinsa ymmärtäminen on välttämätöntä tehokkaan CSP:n laatimiseen.

Katsotaan seuraavaksi, miten skriptien rajoitus tyypillisesti tehdään.

Aloita rajaamalla skriptit joko kokonaan pois tai sallimalla ne vain omasta domainista

Jos sovelluksesi lataa skriptejä omalta palvelimeltaan (useimmat sovellukset tekevät) niin voit sallia sen seuraavasti:

script-src: 'self'

Jos sovelluksesi ei lataa skriptejä omalta palvelimelta lainkaan, voit estää skriptit lähtökohtaisesti kokonaan.

script-src: 'none'

Huomaa, että hipsut ovat tärkeitä. CSP:ssä 'none' tarkoittaa "ei mitään" ja none (ilman hipsuja) tarkoittaa URL-osoitetta, joka alkaa sanalla "none".

Skriptin salliminen URL-osoitteen perusteella

Jos haluat sallia tietyn skriptin Internetistä tai vaikkapa kaikki skriptit tietystä osoitteesta, voi sen tehdä URL-pohjaisella sallimisella. Huomaa, että 'hipsuja' ei käytetä URL-pohjaisessa sallimisessa.

Tietyn skriptin salliminen https://cdn.example.com osoitteesta

script-src: https://cdn.tailwindcss.com/script.js

Kaikkien skriptien salliminen https://cdn.example.com osoitteesta

script-src: https://cdn.tailwindcss.com

Skripten salliminen mistä tahansa HTTPS-osoitteesta:

script-src: https:

Inline-skriptien salliminen CSP:llä turvallisesti

Jos sovelluksesi käyttää inline-skriptejä ja et pääse tai halua niistä eroon, CSP tarjoaa niiden sallimiseen kaksi täysin turvatonta ja kaksi kohtuullisen turvallista tapaa.

Mitä 'inline-skriptit' ovat CSP:ssä?

Inline-skripteillä tarkoitetaan CSP:n kontekstissa käytännössä, mitä tahansa, mitä hyökkääjä voisi ujuttaa HTML:n sekaan, joka suorittaisi skriptin suoraan.

Tyypillisin esimerkki inline-skriptistä on script-tagi, jonka sisällä on JavaScript-koodia:

<script>
  console.log("Tämä on inline-skripti")
</script>

CSP laskee inline-skripteiksi kuitenkin laajan kirjon muitakin XSS-vektoreita, kuten JavaScript event handlerit (<img src=x onerror=alert("XSS")>) tai JavaScript-linkit (<a href="javascript:alert('XSS')">XSS</a>).

Turvattomat tavat

Täysin turvattomat tavat ovat:

  • Olla määrittämättä script-src rajoitusta ollenkaan.
  • Määrittää script-src arvona 'unsafe-inline' joka siis nimensä mukaisesti sallii turvattomasti inline-skriptit ja tekee koko CSP:stä hyödyttömän.

Tärkeä huomio 'unsafe-inline' avainsanasta on, että sen käyttö ei itseasiassa ole aina väärin vaan se on tärkeä osa CSP:n yhteensopivuutta vanhempien selainten kanssa, mutta palataan tähän myöhemmin.

Turvalliset tavat

Turvalliset tavat taas ovat:

  • Tiivisteet (hash): Inline-skripti sallitaan esimerkiksi skriptin sisällön SHA256-tiivisteen perusteella.
  • Noncet (nonce): Inline-skripti (tai ulkoinenkin skripti) sallitaan joka sivun latauksessa uudelleen generoitavalla satunnaisen arvon perusteella joka annetaan atribuuttina script-tageille. Tämä dynaaminen tapa vaatii juuri tietynlaisen, perinteisen mallisen web-sovelluksen, jossa HTML rakennetaan palvelimen päässä jokaisella sivun latauksella.
script-src 'unsafe-inline'

Inline-skriptien salliminen tiivisteellä (hash)

Inline-skriptin salliminen esimerkiksi SHA256-tiivisteellä on melko yksinkertaista. Palauta vain script-src -direktiivissä seuraavanlainen avainsana:

script-src: 'sha256-TÄHÄN_TULEE_BASE64_SKRIPTIN_256_TIIVISTEESTÄ'

Mistä tiivisteen arvo?

Mistä saat Base64-arvon skriptin sisällön SHA256-tiivisteestä? - Helpoin tapa on antaa Google Chrome -selaimen tehdä työ puolestasi. Toimi seuraavasti:

  • Aseta (paikallisessa kehitysympäristössäsi) CSP-otsake, joka estää inline-skriptit kokonaan, esimerkiksi script-src 'none' tai script-src 'self'.
  • Varmista, että skripti, jonka haluat sallia on sivustolla.
  • Lataa sivu Google Chrome -selaimessa. Skripti ei suoridu, koska CSP estää sen. Avaa nyt konsoli ja etsi CSP-virheilmoitus, joka esti skriptin. Virheilmoituksessa on mukana valmiiksi laskettu Base64-SHA256 skriptistä, jonka voit sellaisenaan lisätä script-src direktiiviisi.

Kuvat ovat yhdestä sivun alalaidasta löytyvästä harjoituksesta.

Tärkeä huomio tiivisteistä ja taaksepäin yhteensopivuudesta

Kun sallit skriptin tiivisteellä, 'unsafe-inline' lakkaa automaattisesti olemasta voimasta. Toisin sanoen 'sha256'-avainsana syrjäyttää 'unsafe-inline' avainsanan. Tämä on tärkeää, koska todella vanhat selaimet eivät tue 'sha256'-avainsanaa, joten kannattaa lisätä myös 'unsafe-inline', jos käytät tiivisteitä, ettei sivusto hajoa vanhoilla selaimilla.

Inline-skriptien salliminen satunnaisella tunnisteella (noncella)

Nonce-tyylinen CSP voi sopia hyvin perinteisille web-sovelluksille, jotka rakentavat HTML:ää templaattikirjastolla jokaista HTTP-vastausta varten.

Nonce ei sovi kaikille sovelluksille

Se ei sovellu sovelluksiin, jossa on moderni staattinen frontendi (React, Svelte, Vue, Angular, jne), koska toimiakseen nonce vaatii, että HTML-koodi, jossa script-tagi on, rakennetaan uudestaan jokaisella sivun latauksella. Tämä sulkee ulos myös välimuistin (kuten CDN) käyttämisen HTML-tiedostoille, joissa script-tagit ovat vaikka ne olisivat muuten staattisia.

Noncen toimintaperiaate

Noncet toimivat seuraavanlaisesti:

  • Kun sivu latautuu, luo palvelimella satunnainen tunniste (nonce).
  • Päivitä HTTP-vastaukseen CSP niin, että siinä on script-src 'nonce-ARVO_JONKA_GENEROIT'
  • Päivitä HTTP-vastaukseen rakennetun HTML:n skriptitagit niin, että niissä on attribuutti nonce, jonka arvo on generoitu nonce.

Selaimeen on tarkoitus palautua jotain tämän kaltaista:

HTTP-vastaus
HTTP/1.1 200 OK
Content-Type: text/html
Content-Security-Policy: script-src 'nonce-12345'

<html>
<body>
<script nonce="12345">
    alert("Hey!")
</script>
</body>
</html>

Nonce käytännössä

Jos päädyt tähän vaihtoehtoon, vältä itse nikkaroitua toteutusta, koska siinä voi tehdä helposti virheitä. Suosituille ohjelmistokehyksille on olemassa valmiita kirjastoja, jotka osaavat tehdä tämän puolestasi.

Esimerkiksi Djangolla on django-csp kirjasto, joka osaa tehdä tämän automaattisesti ja saat HTML-templaatteihisi käyttöön request.csp_nonce muuttujan.

<script nonce="{{request.csp_nonce}}">
        var hello="world";
</script>

Taaksepäin yhteensopivuudesta

Kuten tiivisteet (hash), myöskään noncet eivät toimi ihan muinaisilla selaimilla ja syrjäyttävät 'unsafe-inline' avainsanan myös samalla tavalla. 'unsafe-inline' voi siis käyttää sen kanssa yhdessä, jottei sovellus hajoa vanhoilla selaimilla, jotka eivät tue nonceja.

Ulkoisen skriptin salliminen tiivisteellä

Inline-skriptien lisäksi myös ulkoisia skriptejä voi sallia tiivisteillä. Ulkoisella skriptillä siis tarkoitetaan tämän kaltaista:

<script src="https://www.example.com/script.js"/>

Mekanismi on identtinen inline-skriptien kanssa. Lasket vain esimerkiksi SHA256-tiivisteen skriptin sisällöstä, base64-enkoodaat arvon ja asetat sen script-src direktiiviin.

script-src: 'sha256-TÄHÄN_TULEE_BASE64_SKRIPTIN_TIIVISTEESTÄ'

On kuitenkin yksi lisävaatimus ennen kuin tämä toimii: SRI.

Lisävaatimus: SRI (Subresource Integrity)

SRI (Subresource Integrity) on turvakontrolli, jolla selain voidaan ohjeistaa lataamaan ulkoinen skripti vain, jos sen sisältö ei ole muuttunut. Ja odotettu sisältö kerrotaan base64-koodattuna tiivisteenä, ihan kuten CSP:ssäkin.

Tämä ei siis vielä toimi yllä olevan CSP:n kanssa:

<script src="https://www.example.com/script.js"/>

Tarvitaan:

<script integrity="sha256-TÄHÄN_TULEE_BASE64_SKRIPTIN_TIIVISTEESTÄ" src="https://www.example.com/script.js"/>

JavaScript event handlerien (tapahtumankäsittelijöiden) salliminen 'unsafe-hashes' avainsanalla

Inline-skriptejä voi sallia tiivisteillä, mutta oletusarvoisesti tämä ei päde JavaScript tapahtumankäsittelijöihin.

Eli, jos sinulla olisi seuraavanlainen skripti:

<script>alert("Hello")</script>

...voisit sallia sen tiivisteellä seuraavasti:

script-src 'sha256-h0KcPydp+ZJA83Cf3imWAKpq/DAOmTiLCuLkAJsLHhM='

Mutta, jos sinulla onkin tapahtumankäsittelijä:

<button onclick='alert("Hello")'>Klikkaa</button>

...sama CSP ei enää toimisikaan. Paitsi, että sen saa toimimaan, jos haluaa. Voit antaa script-src direktiivissä 'unsafe-hashes' avainsanan, jonka jälkeen myös tapahtumankäsittelijöissä olevat inline-skriptit voidaan sallia tiivisteellä.

script-src 'unsafe-hashes' 'sha256-h0KcPydp+ZJA83Cf3imWAKpq/DAOmTiLCuLkAJsLHhM='

Esimerkki : 'unsafe-hashes'

Sinulla on tällainen nappi etkä voi tai halua refaktoroida sitä muotoon jossa inline-tapahtumankäsittelijää ei tarvittaisi.

 <button onclick="alert('Hello!')">

Sen sijaan, että vähentäisit merkittävästi turvallisuutta lisäämällä 'unsafe-inline' avainsanan script-src -direktiiviin, voit käyttää 'unsafe-hashes' avainsanaa yhdessä tiivisteen kanssa joka sallii funktion, seuraavasti:

script-src 'unsafe-hashes' 'sha256-0KAUGUNSAE9SmnRfyY+2MhbftgtqjBwh4sNjtk8aJKM='

Tiivisteen saat vaikka report-uri palvelusta.

Evalin salliminen

Kun 'unsafe-eval' on sallittuna script-src direktiivissä, se tarkoittaa, että sivusto voi suorittaa JavaScript-koodia, joka sisältää dynaamista koodin suoritusta kuten eval(), Function(), setTimeout(), setInterval(), WebAssembly.compile(), jne.

Jos et käytä JavaScript-kirjastoja, jotka tarvitsevat tällaisia toimintoja, voit jättää sen pois. On kuitenkin yleistä, että se tarvitaan, jolloin voit mainiosti sallia sen.

Pahaenteistä nimeä ei tarvitse säikähtää. 'unsafe-eval' avainsana ei ole vaarallisimmasta päästä. Se kyllä mahdollistaa tietynlaiset DOM-pohjaiset XSS-hyökkäykset, mutta tällaiset hyökkäykset ovat melko harvinaisia.

script-src 'unsafe-inline'

Valmiiksi luotetun skriptin lataamien lisäskriptien salliminen 'strict-dynamic' avainsanalla

On melko yleistä nykyään, että sovellukset lataavat skriptejä, jotka lataavat lisää skriptejä. Esimerkiksi GTM (Google Tag Manager) on tällainen ratkaisu.

Ennen 'strict-dynamic' avainsanaa erityisesti suurien sovellusten CSP:t kasvoivat absurdin kokoisiksi ja niitä oli vaikea ylläpitää. Skriptin lisääminen esimerkiksi GTM:n avulla sivulle pitäisi olla helppoa, mutta eipäs olekaan, kun lisäksi pitää päivittää sovelluksen CSP, joka tyypillisesti vaatii koko sovelluksen uudelleen deploymentin.

Tätä ongelmaa ratkomaan kehitettiin 'strict-dynamic'. Sen vaikutus on seuraavanlainen:

  • Syrjäytetään 'unsafe-inline' -avainsana (se ei enää vaikuta).
  • Syrjäytetään 'self' -avainsana.
  • Syrjäytetään URL-pohjaiset sallimiset.
  • Sallitaan skriptien ladata lisää skriptejä ja niiden lataamien skriptien ladata lisää skriptejä ja näin edelleen, jos ne on sallittu noncella tai hashilla (pitää olla toinen näistä)

Tärkeä huomio taaksepäin yhteensopivuudesta 'strict-dynamic' avainsanaa käyttäessä

Koska 'strict-dynamic' ei ole tuettuna vanhoilla selaimilla, on tärkeää rakentaa CSP niin, että sivusto ei mene rikki vanhoillakaan selaimilla.

Tämä tehdään lisäämällä 'unsafe-inline' ja https: avainsanat script-src -direktiiviin.

Miksi?

  • 'unsafe-inline' siksi, että inline-skriptit latautuisivat sellaisillakin selaimilla, joka ei tue esimerkiksi tiivisteitä ja nonceja.
  • https: siksi, että skriptien lataamat lisäskriptit latautuisivat myös sellaisilla selaimilla, jotka eivät tue 'strict-dynamic' -avainsanaa.
Esimerkki : GTM-salliminen tiivisteellä ja 'strict-dynamic' avainsanalla

Oletetaan että meillä on GTM (Google Tag Manager) käytössä ja tarkoitus on antaa kaikkien GTM:n sivulle lataamien skriptien suorittua.

Aloitetaan lisäämällä GTM-skripti sivulle.

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;var n=d.querySelector('[nonce]');
n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->

Avataan sitten sivu, katsotaan tiiviste Google Chrome -selaimen konsoliin ilmestyneestä herjasta ja sallitaan skripti sitten script-src -direktiivissä. Nyt skripti suoriutuu mutta se ei vielä saa ladata sivulle lisää skriptejä.

Tätä varten lisätään script-src -direktiiviin myös 'strict-dynamic' -avainsana. Nyt valmiiksi luotettu skripti saa ladata vapaasti lisää skriptejä sivulle.

GTM tarvitsee myös 'unsafe-eval' avainsanan toimiakseen.

Lopuksi lisätään 'unsafe-inline' ja https: jotta sivusto toimisi edelleen myös hyvin vanhoilla selaimilla.

script-src 'sha256-abcdefg' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' https:

Näin rajoitat mihin sivustolta voi lähettää lomakkeita

Vaikkei hyökkääjä pääsisikään ajamaan JavaScript-koodia sivulla, pelkästään HTML:n upottaminen sivulle hyökkääjän toimesta on aina vaarallista.

Yksi tekniikka, mitä hyökkääjä saattaa käyttää on rakentaa sivustolle HTML-lomake, joka esittää, että käyttäjän istunto olisi vanhentunut ja pyytää käyttäjää "kirjautumaan uudelleen", lähettäen käyttäjätunnuksen ja salasanan hyökkääjän sivulle.

form-action

form-action -direktiivi rajaa, mihin sivustolta saa lähettää lomakkeita. Esimerkiksi näin voitaisiin rajata lomakkeiden lähettäminen omaan alkuperään ja estää yllä mainittu hyökkäys.

form-action 'self'

Näin rajoitat mitkä sivut saavat kehystää sivun

On olemassa erilaisia selainpuolen hyökkäyksiä, jotka hyödyntävät kehyksiä (kuten iframe), joilla ladataan kohdesivusto hyökkääjän sivuston sisällä.

Yksi tyypillinen esimerkki on nk. "clickjacking" hyökkäys, jossa tuodaan kohdesivu näkymättömänä kehyksenä hyökkääjän sivulle ja huijataan selaimen käyttäjä klikkailemaan asioita kohdesivulla omalla käyttäjätunnuksellaan vaikka uhri luulee klikkailevansa hyökkääjän sivua.

Tällainen kehystys on ennen vanhaan estetty X-Frame-Options -otsakkeella, mutta se on vanhentumassa ja nykyisin tähän käytetään CSP:tä.

frame-ancestors

frame-ancestors-direktiivi määrittää, mistä lähteistä verkkosivun sisältö voidaan upottaa <frame>, <iframe>, <object> tai <embed> -elementteihin.

Esimerkiksi voit määrittää frame-ancestors-direktiivin HTTP-vastauksessa, joka tulee verkkosivustolta:

HTTP-vastaus
Content-Security-Policy: frame-ancestors 'self' https://trusted.example.com;

Tässä esimerkissä frame-ancestors sallii upottamisen ainoastaan sivustoon itseensä ('self') ja lisäksi antaa luvan upottaa sisältöä https://trusted.example.com -sivustolta.

Jos sivustoa ei tarvitse kehystää lainkaan, kuten usein on, käytä arvoa 'none'.

HTTP-vastaus
Content-Security-Policy: frame-ancestors 'none';

Näin rajoitat mistä eri resursseja sivustolla voi ladata

CSP:ssä on paljon direktiivejä, joilla voidaan suojata sivua rajoittamalla mistä resursseja saa ladata.

Tässä ovat direktiivit ja niiden käyttötarkoitukset.

child-src

Määrittelee kelvolliset lähteet web-workereille ja sisäkkäisille selauskonteksteille, jotka on ladattu käyttäen elementtejä kuten <frame> ja <iframe>. Voit käyttää myös frame-src ja worker-src -direktiivejä erikseen, jos haluat rajoittaa vain toista.

connect-src

Rajoittaa URL-osoitteita, joihin JavaScript-koodi voi muodostaa HTTP-yhteyksiä. Tämä tekee esimerkiksi interaktiivisen XSS-komentokanavan muodostamisesta hyökkääjälle hankalampaa.

Tiedon eksfiltorintia (vuotamista hyökkääjälle) ei valitettavasti CSP:llä kuitenkaan voi estää kokonaan, koska JavaScript-koodi voi uudelleenohjata selainikkunan hyökkääjän sivulle ja vuotaa tiedon URL-parametreissa. Tätä suojaamaan oltiin tekemässä navigate-to direktiivi, mutta siitä valitettavasti luovuttiin.

default-src

Toimii varalla muille direktiiveille tässä listauksessa. Tästä on kohta erillinen kappale.

font-src

Määrittää kelvolliset lähteet fonteille (jotka on ladattu esim. käyttäen @font-face CSS-sääntöä).

frame-src

Määrittää kelvolliset lähteet sisäkkäisille selauskonteksteille, jotka ladataan käyttäen elementtejä kuten <frame> ja <iframe>.

img-src

Määrittää kelvolliset lähteet kuville ja ikoneille.

manifest-src

Määrittää kelvolliset lähteet sovelluksen manifestitiedostoille.

media-src

Määrittää kelvolliset lähteet mediatiedostojen lataamiseen käyttäen <audio>, <video> ja <track> elementtejä.

object-src

Määrittää kelvolliset lähteet <object> ja <embed> elementeille. Tällaisia legacy-elementtejä hyvin harvoin nykyään tarvitaan eli kannattaa lähes aina asettaa object-src -direktiivin arvoksi 'none'.

script-src

Määrittää kelvolliset lähteet JavaScript- ja WebAssembly-resursseille. Tämä käytiin läpi kattavasti ylempänä.

script-src-elem

Määrittää kelvolliset lähteet JavaScript <script> -elementeille. Käyttää oletuksena script-src arvoa, joten et tarvitse tätä direktiiviä ellet halua nimenomaan antaa <script>-elementeille erilaisia sääntöjä kuin tapahtumankäsittelijöille. Tyypillisesti et halua.

script-src-attr

Määrittää kelvolliset lähteet JavaScriptin event handlereille (tapahtumankäsittelijöille). Käyttää oletuksena script-src arvoa, joten et tarvitse tätä direktiiviä ellet halua nimenomaan antaa tapahtumankäsittelijöille erilaisia sääntöjä kuin <script>-tageille. Tyypillisesti et halua.

style-src

Määrittää kelvolliset lähteet CSS-tyylitiedostoille. Kuten skripteissä, voit halutessasi käyttää tiivisteitä tai nonceja.

Jos haluat sallia tyylejä style-attribuutilla (<button style="color: red">) tiivisteellä, tarvitaan 'unsafe-hashes' lisäksi.

Esimerkki : style-src

Oletetaan että haluat ladata tyylejä omasta alkuperästä sekä https://cdn.example.com osoitteesta, voisit tehdä seuraavanlaisen politiikan.

style-src 'self' https://cdn.example.com/

style-src-elem

Määrittää kelvolliset lähteet tyylitiedoston <style> -elementeille ja <link> -elementeille, joilla on rel="stylesheet". Käyttää oletuksena style-src arvoa.

style-src-attr

Määrittää kelvolliset lähteet yksittäisten DOM-elementtien sisäisille tyyleille. Käyttää oletuksena style-src arvoa.

worker-src

Määrittää kelvolliset lähteet Worker-, SharedWorker- tai ServiceWorker-skripteille.

Mitkä direktiivit ottavat oletuksena arvonsa default-src direktiivistä?

CSP:n default-src direktiivillä voidaan asettaa oletusarvo seuraaville direktiiveille:

  • child-src
  • connect-src
  • font-src
  • frame-src
  • img-src
  • manifest-src
  • media-src
  • object-src
  • prefetch-src
  • script-src
  • script-src-elem
  • script-src-attr
  • style-src
  • style-src-elem
  • style-src-attr
  • worker-src

Direktiiviä käytetään esimerkiksi seuraavasti. Tällainen CSP asettaisi kaikkien yllä mainittujen direktiivien arvoksi 'self', jolloin kyseisiä resursseja saisi ladata omalta palvelimelta, mutta ei muualta.

HTTP-vastaus
Content-Security-Policy: default-src 'self';

default-src direktiivin arvo ylikirjoittuu resurssin osalta jolle määritellään direktiivi suoraan. Esimerkiksi tässä politiikassa kaikkia muita yllä mainittuja resursseja ei saa ladata ollenkaan, mutta skripetjä voidaan ladata omalta palvelimelta.

HTTP-vastaus
Content-Security-Policy: default-src 'none'; script-src 'self'

Näin rajoitat HTML base-elementin käyttöä

Base-elementtiä on joskus mahdollista väärinkäyttää HTML-injektiohyökkäyksessä niin, että sovelluksen linkit ja lomakkeet ohjataan haitalliselle sivustolle.

"><base href="https://evil.example.com/">

Onneksi CSP:ssä on direktiivi, jolla tämänkin hyökkäysvektorin voi estää.

base-uri

Rajoittaa URL-osoitteita, jotka voidaan käyttää asiakirjan <base>-elementissä.

base-uri 'self'

Näin asetat HTTP-vastauksen hiekkalaatikkoon (sandbox)

Iframe -elementti tukee sandbox -määritettä, jolla voi määrittää kehystetylle sivulle rajoituksia. CSP:n sandbox-direktiivillä voit hyödyntää samoja rajoituksia omalla sivullasi.

sandbox

Asettaa hiekkalaatikon (sandbox) pyydetylle resurssille samankaltaisesti kuin <iframe> -elementin sandbox-attribuutti.

Esimerkiksi:

Content-Security-Policy: sandbox allow-scripts;

Voit lukea eri parametereista, joita voit rajoittaa sandboxilla täältä.

Näin saat ilmoituksia reaaliajassa kun CSP-käytäntö estää jotain

On erittäin hyödyllistä saada tieto siitä, kun CSP-käytäntö estää jotain sovelluksessasi, lähtökohtaisestihan tällaista ei pitäisi tapahtua lainkaan. Tähän tarpeeseen vastaamaan CSP:stä löytyy raportointidirektiivit, report-uri ja report-to. Käytännössä toistaiseksi meitä kiinnostaa vain report-uri, koska report-to on sen tulevaisuuden seuraaja, jota ei ole vielä edes toteutettu kaikkiin selaimiin.

report-uri

Ohjaa käyttäjäagenttia raportoimaan yrityksistä rikkoa CSP:tä. Nämä rikkomusraportit koostuvat JSON-dokumenteista, jotka lähetetään HTTP POST -pyynnöllä määritettyyn URI:in.

Content-Security-Policy: report-uri https://example.com/csp-reports

Onko report-uri vanhentunut?

CSP:n report-uri direktiivi ollaan näillä näkymin korvaamassa report-to -direktiivillä tulevaisuudessa, mutta report-to ei vielä edes tueta kaikissa selaimessa, joten käytä vain report-uri direktiiviä toistaiseksi.

Voit katsoa tämän hetken tilanteen report-to direktiivin tuesta täältä.

CSP-poikkeamaraporttien sisältö

Poikkeamaraportit lähetetään POST-pyyntönä report-uri direktiivissä määrittelemääsi URL-osoitteeseen. Pyynnön Content-Type on application/csp-report ja runko sisältää seuraavan JSON-rakenteen:


blocked-uri

Se resurssin URI, joka estettiin latautumasta Content Security Policy -käytännön vuoksi. Jos estetty URI on eri alkuperästä kuin dokumentin URI, estetty URI katkaistaan sisältämään vain protokolla, hosti ja portti.

disposition

Joko "enforce" (toteuta) tai "report" (raportoi) riippuen siitä, käytetäänkö Content-Security-Policy-otsikkoa vai Content-Security-Policy-Report-Only -otsikkoa.

document-uri

Dokumentin URI, jossa rikkomus tapahtui.

effective-directive

Ohje, jonka täytäntöönpano aiheutti rikkomuksen.

original-policy

Rikottu CSP-käytäntö.

referrer

Jos sivustolle on siirrytty toiselta sivustolta, kun rikkomus tapahtui, referrer-kenttä sisältää tuon sivuston URL-osoitteen

script-sample

Ensimmäiset 40 merkkiä JavaScript-koodista tai tyylistä, joka aiheutti poikkeaman.

status-code

Poikkeaman aiheuttaneen sivun latauksen HTTP-tilakoodi.

CSP-poikkeamaraporttien käsittely on näppärää Sentry.io -palvelussa

Jos käytät Sentryä (sentry.io) saat CSP-raportit lähetettyä suoraan Sentryyn, jossa niistä tulee omia issueita. Voit lukea tästä lisää täältä.

Käytännössä saat Sentryltä valmiin report-uri direktiivin arvon.

Uhkien havaitseminen CSP-poikkeamaraporteilla Report URI -palvelussa

Jos et käytä Sentryä jo valmiiksi vaan haluat pelkästään CSP-raportit niin Report URI (https://report-uri.com/) on hyvä vaihtoehto. Tavanomaisen CSP-raportoinnin lisäksi palvelu voi käyttää report-only tilassa (katsotaan tämä alempana) olevia CSP-politiikkoja kekseliäästi web-sovelluksen epäilyttävän käyttäytymisen monitoroimiseen selaimen päässä.

CSP-poikkeamaraporttien käsittely manuaalisesti

Poikkeamien käsittely voidaan tehdä manuaalisestikin, jos ei halua käyttää esimerkiksi Sentryä. Tässä on esimerkki koodista, joka käsittelee CSP-poikkeamaraportteja ja tulostaa kentät:

@app.route('/csp-report', methods=['POST'])
def handle_csp_report():
    if request.method == 'POST':
        # Hae CSP-poikkeamaraportin tiedot pyynnöstä
        csp_report_data = request.get_json()

        # Käsittele CSP-poikkeamaraportin tiedot
        process_csp_report(csp_report_data)

        # Vastaa selaimelle 200 OK -tilalla
        return jsonify({'status': 'success'}), 200

def process_csp_report(csp_report_data):
    # Tulosta CSP-poikkeamaraportin kentät
    print("Vastaanotettu CSP-poikkeamaraportti:")
    print(f"Tapahtuman tyyppi: {csp_report_data.get('type', '')}")
    print(f"Tiedoston lähde: {csp_report_data.get('document-uri', '')}")
    print(f"Rajoitukset: {csp_report_data.get('blocked-uri', '')}")
    print(f"Aikaleima: {csp_report_data.get('timestamp', '')}")
    print(f"Viesti: {csp_report_data.get('message', '')}")

Näin määrität CSP-politiikan report-only tilassa

CSP-käytäntö voidaan antaa myös report-only -tilassa, jolloin CSP ei vielä estä mitään sivulta, mutta lokittaa silti konsoliin ja raportoi report-uri direktiivin mukaisesti, niin kuin jotain olisi estetty. Tämä on suositeltavaa, kun otat ensimmäistä kertaa CSP:n käyttöön tuotannossa olevaan sovellukseen. Voit vaihtaa politiikan estävään tilaan, kun olet varmistunut, ettei se hajota sovellusta luotetuille käyttäjille.

Voit tehdä tämän käyttämällä Content-Security-Policy -otsakkeen sijasta Content-Security-Policy-Report-Only -otsaketta.

HTTP-Pyyntö
Content-Security-Policy-Report-Only: csp-käytäntö

Voit määrittää sekä Content-Security-Policy että Content-Security-Read-Only otsakkeet

Jos olet esimerkiksi tekemässä olemassa olevaan CSP-policyyn suuria muutoksia, voit viedä muutokset tuotantoon ensin Content-Security-Policy-Report-Only otsakkeessa ja myöhemmin päivittää Content-Security-Policy otsakkeen, jos ongelmia ei ilmene. Otsakkeet ovat täysin riippumattomia toisistaan ja voit vapaasti asettaa molemmat samassa HTTP-vastauksessa.

Content Security Policyn voi määrittää myös meta-tagilla

Jos ei jostain syystä pääse lisäämään HTTP-vastauksiin otsakkeita, mutta haluaa silti suojata sivustonsa CSP-politiikalla, tämän voi tehdä HTML:ssä meta-tagilla seuraavasti.

<meta http-equiv="Content-Security-Policy" content="policy" />

Tällainen CSP on hieman rajoitetumpi kuin HTTP-otsakkeella määritetty, mutta voit silti hyödyntää melkein kaikkia suojia. Et voi kuitenkaan käyttää report-uri, report-to, frame-ancestors etkä sandbox -direktiivejä.

Yhteenveto ja jatkolukemista

CSP on yksinkertaisesta toimintaperiaatteestaan huolimatta yllättävän monimutkainen työkalu. Se on kuitenkin valtavan tärkeä lisäsuoja XSS-hyökkäyksiä vastaan ja kaikkien web-sovelluksien kannattaa ehdottomasti ottaa se käyttöön.

CSP-käytäntöä rakentaessa saattaa törmätä kysymyksiin, joita ei tässä moduulissa tullut käsiteltyä. Tällöin paras paikka lukea on suoraan määrittelystä, joka on varsin luettava ja sisältää paljon hyviä esimerkkejä: https://w3c.github.io/webappsec-csp/

Tee seuraavaksi alla olevat harjoitukset, niin saat käytännön kokemusta CSP:n rakentamisesta!

Harjoitukset

CSP Haaste - Taso 1

Voi ei! Joku on hakkeroinut verkkosivuston ja lisännyt sivulle haitallisen skriptin. Osaatko korjata CSP:n estääksesi skriptin suorittamisen ja pitääksesi muun verkkosivuston toiminnassa? Estä samalla koiran kuvan näkyminen, mutta pidä kissa.

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 2

Voi ei! Joku on hakkeroinut verkkosivuston ja lisännyt sivulle haitallisen skriptin. Osaatko korjata CSP:n estääksesi skriptin suorittamisen, sallien silti sivulle kuuluvan upotetun skriptin suorittamisen?

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 3

Voi ei! Joku on hakkeroitunut verkkosivuston ja lisännyt sivulle kolme haitallista skriptiä, yhden inline-skriptinä suoraan sivulle, toisen internetistä ja kolmannen sovelluksen omasta verkkotunnuksesta! Osaatko korjata CSP:n estääksesi pahantahtoisten skriptien suorituksen ja silti pitää muun verkkosivuston toiminnallisena? Ennen kaikkea oletetaan, ettet tiedä, mitä skriptejä legitiimi inline-skripti tuo mukanaan (kuten usein tapahtuu kolmannen osapuolen skripteissä, esimerkiksi Google Tag Manager), joten sen tulisi pystyä lataamaan mitä tahansa skriptejä Internetistä.

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 4

Voi ei! Lähetit CSP-otsikon, jonka rakensit tasolla 3, QA-tiimille, ja he sanoivat, että se rikkoi täysin verkkosivuston vanhoilla selaimilla! Osaatko korjata CSP:n lisäämällä taaksepäin yhteensopivuuden vanhoille selaimille?

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 5

Voi ei! Joku on hakkeroitunut verkkosivuston ja injektoinut väärennetyn kirjautumislomakkeen sivulle. Se lähettää käyttäjän tunnistetiedot hyökkääjän palvelimelle. Osaatko korjata CSP:n estääksesi lomakkeen lähettämisen?

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 6

Voi ei! Joku on hakkeroitunut verkkosivuston ja injektoinut base-tagin sivulle. Se muuttaa sivun perus-URL:ää, ohjaten kaikki linkit hyökkääjän sivulle. Osaatko korjata CSP:n estääksesi base-tagin käytön? Samalla voit estää kehykset ulkoisista lähteistä ja estää perinteisten HTML-elementtien, kuten embed tai object, käytön sivulla. Sinulla saattaa olla ongelmia CSP:n päivityksen kanssa väärennetyn base-tagin vuoksi. Ehkäpä sinun tulisi käyttää selaimen kehittäjätyökaluja poistaaksesi base-tagin sivulta ennen päivityslomakkeen lähettämistä?

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 7


Voi ei! Joku on hakkeroitunut verkkosivuston ja injektoinut CSS-tiedoston, joka tekee kaikesta näyttämään 90-luvun verkkosivulta. Osaatko korjata CSP:n estääksesi CSS-tiedoston lataamisen?

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 8

Voi ei! Joku on hakkeroitunut verkkosivuston ja injektoinut kirjastokortin tiedot keräävän skriptin sivulle, joka lähettää asiakkaiden kirjastokorttitiedot hyökkääjän palvelimelle. Osaatko korjata CSP:n estääksesi HTTP-yhteyden muodostamisen hyökkääjän palvelimeen?

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 9

Voi ei! Turvatiimi on ilmoittanut, että verkkosivusto on altis clickjacking-hyökkäyksille. Osaatko korjata CSP:n estääksesi verkkosivuston latautumisen iframeen?

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

CSP Haaste - Taso 10

Viimeinen haaste. Sinut palkataan yritykseen toteuttamaan tiukka, taaksepäin yhteensopiva Content Security Policy (CSP) heidän verkkosivustolleen. Onnistutko siinä?

Tehtävät

Flag

Löydä lippu (flag) labraympäristöstä ja syötä se alle.

hakatemia pro

Valmis ryhtymään eettiseksi hakkeriksi?
Aloita jo tänään.

Hakatemian jäsenenä saat rajoittamattoman pääsyn Hakatemian moduuleihin, harjoituksiin ja työkaluihin, sekä pääset discord-kanavalle jossa voit pyytää apua sekä ohjaajilta että muilta Hakatemian jäseniltä.