Tiukan Content Security Policyn (CSP) saavuttaminen Sisussa
Käytämme Sisussa tiukkaa CSP-määritystä, jolla estetään verkkoselaimessa käyttäjään kohdistuvia tietoturvahyökkäyksiä.
Content Security Policy on tapa estää verkkoselaimessa käyttäjään kohdistuvia tietoturvahyökkäyksiä. Vaikka yleisesti käytetyt selaimet ovat tukeneet CSP:tä useampia vuosia, sen käyttö ja tuntemus eivät tunnu olevan yhtä yleistä kuin monien muiden tietoturvakäytäntöjen. Siksi sen käyttöönotto ei aina ole mutkatonta, ja tiukkojen rajoitusten käyttöönotto Sisussa onkin vaatinut meiltä ylimääräistä työtä.
Mitä CSP tekee?
CSP:llä rajoitetaan verkkosivuilla sallittu sisältö vain tietyistä, luotetuista lähteistä tuleviin, jolloin muualta tulevaa sisältöä (JavaScript-koodi, tyylimäärittelyt, kuvat jne.) ei ajeta tai näytetä. Kun selain estää CSP:n perusteella jotain tapahtumasta, selaimen konsoliin tulee siitä kertova virheviesti.
CSP:llä voidaan estää pääasiassa cross-site scripting (XSS) -hyökkäyksiä, joiden avulla hyökkääjä voi ajaa omaa koodiaan sivustolla, varastaa tietoja tai muuttaa sivun sisältöä käyttäjän hämäämiseksi, esimerkiksi linkkaamaan huijaussivustolle. Näin voi käydä, jos sivulla esimerkiksi näytetään käyttäjien luomaa sisältöä ilman, että siitä on poistettu kaikki mahdollisesti haitallinen sisältö.
CSP:tä voidaan ajatella ylimääräisenä varotoimena, turvaverkkona, jota ei periaatteessa tarvita, jos tietoturva on muilta osin hoidettu täysin moitteettomasti. Katkeamatta aukoton tietoturva on kuitenkin haastava saavuttaa, ja siksi useamman puolustautumistavan käyttö on suositeltavaa (ns. defense in depth -periaate).
Miten CSP määritellään?
CSP otetaan yleensä käyttöön lisäämällä HTML-sivupyynnön vastaukseen Content-Security-Policy-HTTP-otsake, jonka arvot määrittelevät rajoitukset eri resurssityypeille.
Tarkoitan tässä kirjoituksessa termillä "tiukka CSP" sitä, että koodi- (script-src) ja tyylittelyresurssit (style-src) on rajoitettu 'self'-arvolla, joka on tiukin määritys, joka ylipäänsä sallii minkäänlaisen lataamisen. Self-arvo tarkoittaa, että:
- lataus on sallittu vain samasta osoitteesta tulevista tiedostoista kuin itse HTML-sivutiedostokin
- resursseja ei saa määritellä suoraan HTML-dokumentin sisällä (ns. inline-määrittely); tämä kieltää mm. script-elementin sisällä määritellyn koodin ajamisen sekä style-attribuutin käytön HTML:ssä
- dynaamisesti luotua koodia ei ajeta (esim. JavaScriptin eval-funktiolla)
Myös sallivammat määrittelyt ovat mahdollisia, esimerkiksi 'unsafe-inline'-arvolla sallittaisiin kaikki edellä manitut inline-määrittelyt. Lisäksi yksittäisiä inline-määrittelyjä voidaan sallia joko laskemalla niiden sisällöstä tiivistearvo (hash) tai luomalla niille yksilöivä tunniste (nonce), joka lisätään CSP:ssä sallittujen listalle.
CSP on myös mahdollista ottaa käyttöön vain raportointitilassa, jolloin se ei estä mitään, mutta näyttää yhä estoviestin selaimessa sekä lähettää "olisin estänyt tämän" -raportin määriteltyyn osoitteeseen. Näin voidaan seurata, mitä seurauksia CSP:n päälle laittamisella olisi ennen sen varsinaista käyttöönottoa. Esto- ja raportointitilat voivat myös olla käytössä samaan aikaan.
Tiukan CSP:n käytön haasteet Sisussa
Jotta tiukkaa CSP:tä on mahdollista käyttää, täytyy oman koodin lisäksi myös projektissa käytetyt ulkoiset ohjelmistokehykset (framework) ja -kirjastot toteuttaa yhteensopivasti. Yleisin syy yhteensopimattomuuteen on koodin tai tyylittelyjen lisääminen inline-määrittelyinä suoraan HTML-tiedostoon. Syynä tähän voi olla esim. suorituskyky tai ratkaisun helppous verrattuna muihin vaihtoehtoihin. Kehittäjä ei myöskään välttämättä vain ole tullut ajatelleeksi CSP-rajoituksia.
Kuten aiemmassa blogikirjoituksessa kuvattiin, Sisussa käytetään käyttöliittymän tekemiseen yleisiä AngularJS- ja Angular-ohjelmistokehyksiä, ja niiden käyttö rajaa myös monen muun ulkoisen kirjaston valintaa. Kuinka CSP-yhteensopivia ne ovat?
AngularJS tukee tiukkaa CSP:tä pienin muutoksin
AngularJS käyttää lähinnä suorituskykysyistä oletuksena tekniikoita, jotka eivät ole yhteensopivia tiukan CSP-määrityksen kanssa. Se tarjoaa kuitenkin helpon tavan käyttää vaihtoehtoista CSP-tilaa, joka toimii sisäisesti eri tavalla, mutta ei vaikuta kehitystyöhön. Tilan saa päälle lisäämällä ng-csp-attribuutin sovelluksen HTML-tiedostoon sekä lataamalla ylimääräiset tyylimäärittelyt angular-csp.css-tiedostosta.
Angular ei tue tiukkaa CSP:tä
Uudemmasta Angularista ei löydy vastaavaa CSP-tilaa kuin vanhemmasta AngularJS:stä, ja vähän yllättäen se ei itse asiassa tuekaan tiukkaa CSP-määritystä. Tämä selviää Angularin dokumentaatioon kaavailluista CSP-tarkennuksista, joissa mainittu "We should clearly state that allowing 'unsafe-inline' in style-src is required for Angular applications." paljastaa, että tiukka CSP ei ole tuettu tyylimäärityksille.
Tyylimääritysten hyökkäyspinta-ala on paljon pienempi kuin JavaScript-koodin, mutta niiden avulla on mahdollista lähettää sivulla olevaa tietoa muualle ovelilla keinoilla. Niinpä tyylimääritystenkin suhteen on hyvä pitää mahdollisimman tiukkaa linjaa.
Angularin yhteensopimattomuuden kiertäminen
Miten siis onnistuimme Angularin yhteensopimattomuudesta huolimatta käyttämään sitä tiukan CSP-määrityksemme kanssa? Ensiksi tutkimme, miksi tiukka CSP ei toimi Angularissa, ja syyksi selvisi yksi Angularin ominaisuus, komponenttityylit, jotka käyttävät inline-tyylimäärittelyitä. Päätimme olla ottamatta tätä ominaisuutta käyttöön, mikä itse asiassa tarkoittikin vain entiseen malliin jatkamista tyylittelyjen suhteen. Teimme myös oman TSLint-säännön, joka tarkistaa, ettei komponenttityylejä käytetä.
Komponenttityylit ovat kuitenkin Angularin oletuskäytäntö, joten ulkoiset Angular-kirjastot käyttävät niitä. Kirjastoja käytettäessä meillä ei ole vastaavaa muokkausvapautta kuin oman koodimme kanssa, mutta niitä on kuitenkin mahdollista muokata siten, että nekään eivät enää käytä komponenttityylejä. Teemme sen paikkaamalla (patch) kirjastoja käyttäen patch-package-kirjastoa.
Paikkauksissa tehtävät muutokset
Paikkauksessa poistamme Angularin komponenttityylimääritykset kirjaston lähdekooditiedostoista ja korvaamme ne vastaavilla tyylimäärittelyillä CSS-tiedostossa. Lisäksi Angular-kirjastot sisältävät lähdekoodista luotuja metatieto-JSON-tiedostoja, joista pitää samoin poistaa komponenttityylimääritykset. Siihen pystymme onneksi käyttämään itse kirjoittamaamme pientä skriptiä, koska JSON-muotoisen tiedon käsittely on huomattavasti yksinkertaisempaa kuin lähdekoodin.
Osa ulkopuolisista kirjastoistamme ei ole Angular-kirjastoja vaan ohjelmistokehysriippumattomia. Kahdessa niistäkin käytetään inline-tyylimäärityksiä, joiden CSP-yhteensopimattomuudesta on myös tehty kyseisiin projekteihin muutospyynnöt GitHub-issueina, mutta joko muutoksia ei ole tehty tai emme ole voineet ottaa niitä sisältävää versiota käyttöön. Ensimmäisessä tapauksissa inline-tyylimäärityksillä ei ole meille mitään vaikutusta, joten olemme vain poistaneet ne ilman korvaavuuksia. Toisessa tapauksessa olemme muuttaneet alkuperäistä koodia niin, että se lisää HTML-elementteihin luokkanimiä, joiden perusteella tyylittelemme ne omissa tyylitiedostoissamme.
Paikkaaminen on siis vahvasti tapauskohtaista, ja paikkojen ylläpidon helppous on paljon tärkeämpää kuin niiden kauneus tai täydellisyys. Paikkoja ei tarvitse katsella tai ajatella kuin kirjastoja päivittäessä eli ne ovat vahvasti "konepellin alla".
Vaihtoehto paikkaukselle
Jos paikkojen ylläpidosta tulee liian monimutkaista, vaihtoehtona on ottaa kirjastosta oma kopio (fork GitHub-termistössä), jolloin muutokset voi tehdä lähdekoodiin perinteisempään malliin esim. erillisessä Git-haarassa. Tällöin kopiota käytetään projektin riippuvuutena alkuperäisen kirjastopaketin sijaan.
Haittapuolena voi olla, että kirjasto pitää paketoida julkaisuvalmiiksi muutosten jälkeen uudelleen. Lisäksi muutokset ovat erillisessä koodirepositoriossa, jonka päivittäminen ei ole aivan yhtä suoraviivaista kuin pelkkien paikkaustiedostojen. Näistä syistä olemme arvioineet paikkaamisen meille ainakin toistaiseksi yksinkertaisemmaksi tavaksi.
Yhteenveto
Tiukan Content Security Policyn käyttö Sisussa on siis vaatinut joidenkin käyttämiemme ulkoisten kirjastojen muokkausta. Muokkaustarpeet ovat onneksi olleet toistaiseksi aika pieniä, ja olemme pystyneet tekemään ne paikkaamalla. Harmittavasti käyttämämme Angular-ohjelmistokehys ei tue tiukkaa CSP:tä tyylimääritysten suhteen, mutta Angularia kehitetään jatkuvasti ja tilanne voi parantua tulevaisuudessa, esimerkiksi jos yksittäisten inline-määritysten sallimista turvallisesti helpotetaan.