Selainturvallisuus

Same Origin Policy (SOP)

Keskitaso
2 h 0 min

Mikä on Same Origin Policy?

Same Origin Policy (Suoraan käännettynä sama alkuperäpolitiikka) eli SOP on joukko suunnitteluperiaatteita, jotka säätelevät selaimen ominaisuuksien toteuttamista.

Sen tarkoituksena on eristää selainikkunat (ja välilehdet) toisistaan niin, että esimerkiksi kun siirryt esimerkki.com, verkkosivusto ei voi lukea sähköpostiviestejäsi Gmail.com -sivustolta, jonka olet saattanut avata toisessa välilehdessä.

Jos SOP:ta ei olisi

Jos SOP (Same Origin Policy) puuttuisi selaimesta, niin Internetistä tulisi varsinainen villi länsi. Sinulla voisi olla verkkopankki auki yhdessä välilehdessä, Facebook toisessa, ja kolmannessa jonkun blogi. Blogi voisi asioida verkkopankissa puolestasi, varastaa kaikki rahasi, ja tehdä ryöstöstä vielä Facebook päivityksen seinällesi.

SOP estää sivustoja, jotka ovat eri alkuperistä, varastamasta tietoja tai suorittamasta ei-toivottuja toimintoja toisissaan.

Mikä on Alkuperä?

Alkuperän (origin) määritelmä on yksinkertainen. Kaksi verkkosivustoa ovat samasta alkuperästä, jos niiden skeema (http: //, https: // jne.), Host/Isäntä (esim. www.hakatemia.fi) ja portti (esim. 443) ovat samat. Löydät määritelmän RFC6545 - Dokumentista (Web Origin Concept).

Implisiittiset portit

Jos porttia ei ole määritetty, se on implisiittisesti 80 HTTP: lle ja 443 HTTPS: lle. Voit lukea lisää URL-osoitteen rakenteesta tästä.

Esimerkkejä

Nämä URL-osoitteet ovat samaa alkuperää (kaikkien skeema on https, host www.hakatemia.fi ja portti 443):

  • https://www.hakatemia.fi/
  • https://www.hakatemia.fi/courses/selainturvallisuus/same-origin-policy-sop
  • https://www.hakatemia.fi:443/courses/selainturvallisuus/same-origin-policy-sop

Ja nämä osoitteet ovat eri alkuperää:

  • http://www.hakatemia.fi/ (väärä skeema ja väärä portti)
  • https://hakatemia.fi/ (väärä host)
  • https://hakatemia.org/ (väärä host)
  • https://www.hakatemia.fi:8080/ (väärä portti)

Mitä SOP sallii ja mitä ei?

Yleensä kirjoittaminen on sallittua ja lukeminen kielletty. Se, mitä tämä milloinkin tarkalleen tarkoittaa, riippuu selaimen ominaisuudesta, joten katsotaanpa joitain esimerkkejä.

JavaScript-ikkunan käyttö

On monia tapoja, joilla verkkosivusto voi saada kahvan (handle) toiseen ikkunaan. Voit kuitenkin rajoittaa tätä käyttämällä COOP-käytäntöä (Cross-Origin Opener Policy) ja CSP:n (Content Security Policy) frame-ancestors direktiiviä.

Näitä menetelmiä ovat mm.

  • Käyttämällä window.open.
  • Kehyksen luominen (kuten olemme tekemässä).
  • Windows.openerin käyttäminen, jos verkkosivusto on kehystetty toisella.
  • Vastaanotettu postMessage event.source.

Tämä kahva tarjoaa pääsyn ikkunan ja sijaintiobjektien kuorittuihin versioihin.

Suoritetaan joitain kokeita osoitteessa http://a.local. Aloitamme hankkimalla ikkunan kahvan osoitteeseen http://b.local luomalla cross-origin (vierasta alkuperää olevan) kehyksen seuraavasti:

var crossOriginFrame = document.createElement('iframe');
crossOriginFrame.src = 'http://b.local';
document.body.appendChild(crossOriginFrame);
var handle = crossOriginFrame.contentWindow

Katsotaan nyt, mitä voimme tehdä sillä.

Ei sallittu: Lue eri alkuperää olevaa sisältöä

console.log(handle.document.body.innerHTML); 

❌ Uncaught DOME-poikkeus: Estetty kehystä, jonka alkuperä on "http://a.local", käyttämästä eri alkuperää olevaa kehystä.

Ei sallittu: Kirjoita eri alkuperää olevaa sisältöä

handle.document.body.innerHTML = "<h1>Hakkeroitu</h1>";

❌ Uncaught DOME-poikkeus: Estetty kehystä, jonka alkuperä on "http://a.local", käyttämästä eri alkuperää olevaa kehystä.

👍 Sallittu: Lue kehysten lukumäärä ristiinlähtöikkunassa

console.log(handle.frames.length); 

✅ 2

☠ Turvallisuusvaikutus: Kehysten laskeminen mahdollistaa joissain tapauksissa yksinkertaisten tietojen vuotamisen vieraalta sivulta.

Ei sallittu: Lue useiden lähteiden URI

console.log(handle.location.href);

❌ Sieppaamaton DOMEpoikkeus: Estetty kehystä, jonka alkuperä on "http://a.local", käyttämästä eri alkuperää olevaa kehystä.

👍 Sallittu: Kirjoita toistensa alkujen URI

handle.location.replace('https://www.example.com')

☠ Turvallisuusvaikutus: Web-sivustoille, jotka kehystät verkkosivustollesi, voidaan saada ikkunakahva window.opener-ominaisuuden kautta. Tämä tarkoittaa, että jos lataat haitallisen verkkosivuston verkkosivustosi iframe-kehykseen, kehys voi muuttaa sivustosi URI:n esimerkiksi tietojenkalastelusivuksi (sivusi klooni, joka esim. varastaa käyttäjiesi salasanat tai pakottaa heidät lataamaan jotain ilkeää). Voit estää tämän käyttämällä sandbox-iframe-kehyksiä.

👍 Sallittu: Viestit ikkunaan postMessagen kautta

PostMessage-menetelmä sallii ristiin lähtevien ikkunoiden kommunikoida keskenään.

// osoitteessa http://b.local/
window.addEventListener(
  'message',
  (event) => {
    document.write('Got message: ' + event.data)
  },
  false
)

// osoitteessa http://a.local/
handle.postMessage('hello', 'http://b.local')

Ei sallittu: Lue localStorage tai sessionStorage

console.log(handle.localStorage);

❌ Uncaught DOME-poikkeus: Estetty kehystä, jonka alkuperä on "http://a.local", käyttämästä eri alkuperää olevaa kehystä.

console.log(handle.sessionStorage);

❌ Uncaught DOME-poikkeus: Estetty kehystä, jonka alkuperä on "http://a.local", käyttämästä eri alkuperää olevaa kehystä.

Resurssien upottaminen ja JavaScript-käyttö

Yleensä minkä tahansa resurssin (kuvan, tyylin, skriptin jne.) upottaminen on sallittua eri alkuperien välillä, mutta JavaScript ei voi käyttää resurssia suoraan. Voit kuitenkin rajoittaa tätä CORP:lla (Cross-Origin Resource Policy).

Lisäksi resursseja upotettaessa lähetetään pyynnön mukana selaimen käyttäjän evästeet upotetun resurssin sivustolle. Tämä antaa verkkosivustoille mahdollisuuden lähettää tunnistetietoja (evästeiden avulla) sivustojen väliseen GET- ja HEAD-pyyntöihin.

☠ Turvallisuusvaikutus: Se, että selaimet lähettävät evästeitä näiden pyyntöjen mukana, mahdollistaa CSRF-hyökkäykset (Cross-Site Request Forgery), jos verkkosivustosi sallii toimien suorittamisen (esim. siirtää rahaa, vaihtaa salasanaa, poistaa tiliä) GET-pyyntöjen kautta (mitä sen ei tietenkään pitäisi sallia).

Katsotaanpa muutama esimerkki sivustojen välisistä resursseista.

👍 Sallittu: kuvan näyttäminen

<img id="cross-origin-image" src="http://b.local/apina.png" />

👍 Sallittu: Luo kuvasta canvas

Vieraasta alkuperästä ladatusta kuvasta voidaan luoda canvas-objekti.

var crossOriginImage = document.getElementById('cross-origin-image')
var canvas = document.createElement('canvas')
canvas.width = crossOriginImage.width
canvas.height = crossOriginImage.height
canvas
  .getContext('2d')
  .drawImage(crossOriginImage, 0, 0, crossOriginImage.width, crossOriginImage.height)
document.body.appendChild(canvas)

Ei sallittu: lue pikseleitä canvakselta

Tämä ei onnistu, skriptillä ei ole suoraa pääsyä vieraan alkuperän tietoihin. Selain heittää tässä tapauksessa virheilmoituksen että canvas on "saastunut" tiedoista jotka on ladattu vieraasta alkuperästä, joten JavaScriptilla ei ole asiaa lukea canvaksen pikseleitä.

canvas.getContext('2d').getImageData(1, 1, 1, 1).data; 

❌ Uncaught DOME-poikkeus: GetImageData-toiminnon suorittaminen CanvasRenderingContext2D:ssä epäonnistui: Kanvas on saastunut eri alkuperätiedoista.

👍 Sallittu: Tyylin lataaminen

Tyylejä voi tietysti ladata vieraista alkuperistä.

<link rel="stylesheet" href="http://b.local/test.css"/>

Ei sallittu: Lue tyylin sisältö

Vieraasta alkuperästä ladattujen tyylien CSS-sääntöjä ei kuitenkaan voi suoraan lukea JavaScriptilla.

console.log(document.styleSheets[0].cssRules); 

❌ Uncaught DOME-poikkeus: 'cssRules'-ominaisuuden lukeminen 'CSSStyleSheet'-tiedostosta epäonnistui: Sääntöihin ei pääse käsiksi osoitteessa <anonymous>:1:25

👍 Sallittu: Skriptien lataaminen

<script id="cross-origin-script" src="http://b.local/test.js"></script>

Tässä on test.js:n sisältö:

var x = 5

Ei sallittu: Skriptin lähdekoodin lukeminen

Vieraasta alkuperästä ladatun skriptin lähdekoodia ei ole mahdollista lukea JavaScriptillä.

👍 Sallittu: Pääsy skriptin tarjoamiin tietoihin ja toimintoihin

Skriptissä oli x-muuttuja, muistatko? Voimme käyttää sitä nyt sivullamme.

console.log(x)
5

JSONP toimi pohjimmiltaan näin (älä käytä sitä, se ei ole koskaan ollut hyvä idea, ja nykyään meillä on parempia tapoja, jotka näet hetken kuluttua).

☠ Turvallisuusvaikutus: Se, että selaimet sallivat pääsyn verkkotunnusten välisten skriptien tarjoamiin tietoihin/toimintoihin, mahdollistaa XSSI-hyökkäykset (Cross-Site Script Inclusion), jos verkkosivustosi tarjoaa dynaamisia JavaScript-tiedostoja, joissa on todennettuja käyttäjätietoja. Älä siis generoi skriptejä lennosta käyttäjädatalla, skriptien kuuluu olla staattisia.

HTML-lomakkeet

Aiemmin tarkastelimme, kuinka vieraasta alkuperästä ladattujen resurssien upottaminen antoi haitallisille verkkosivustoille mahdollisuuden lähettää evästeitä sisältäviä GET-pyyntöjä selaimen käyttäjän puolesta. Nyt näet, kuinka HTML-lomakkeet mahdollistavat evästeiden (kuten istuntotunnisteet) lähettämisen POST-pyyntöjen mukana.

☠ Turvallisuusvaikutus: Tämä toiminta on ensisijainen syy siihen, että CSRF-haavoittuvuuksia on olemassa. Onneksi tilanne on vihdoin paranemassa, kun SameSite-evästeet alkavat olla oletusarvoisesti käytössä.

👍 Sallittu: Lähetä evästeillä varustettu urlenkoodattu HTML-lomake

Oletetaan, että meillä on seuraava lomake osoitteessa http://a.local ja käyttäjällä on aktiivinen istunto osoitteessa http://b.local:

<form method="POST" action="http://b.local/transferFunds">
  <input name="amount" type="text" value="10000" />
  <input name="iban" type="text" value="HACKERBANK1337" />
  <input type="submit" value="Send" />
</form>

Kun käyttäjä napsauttaa "Send" (lähetä)-painiketta, tällainen HTTP-pyyntö lähetetään osoitteeseen http://b.local:

HTTP-Pyyntö
POST /transferFunds HTTP/1.1
Host: b.local
Cookie: SESSIONID=s3cr3t
Content-Type: application/x-www-form-urlencoded
...
amount=10000&iban=HACKERBANK1337

Ja sovellus lähettää rahat, kuvitellen että pyyntö tuli käyttäjän itsensä tahdosta.

👍 Sallittu: Lähetä evästeillä varustettu multipart -muotoinen HTML-lomake

Kuten url-enkoodattu lomake, multipart-muotoinen lomake voidaan lähettää vieraalta sivustolta ilman ongelmia.

<form method="POST" action="http://b.local/transferFunds" enctype="multipart/form-data">
  <input name="amount" type="text" value="10000" />
  <input name="iban" type="text" value="HACKERBANK1337" />
  <input type="submit" value="Send" />
</form>

Ei sallittu: Lähetä evästeillä varustettu JSON HTML -lomake

Sisältötyypillä (content type) "application/json" ei voi lähettää evästeillä varustettuja POST-pyyntöjä vieraalta sivustolta. Evästeet eivät lähde tällaisen HTTP-pyynnön mukana lainkaan.

☠ Turvallisuusvaikutus: Jos sovellus ei tarkista HTTP-viestin sisältötyyppiä (content-type) oikein, se voi tulkita tällaisen POST-pyynnön kelvolliseksi JSON-viestiksi.

XHR- ja Fetch -pyynnöt

👍 Sallittu: evästeillä varustettujen GET-, HEAD- ja POST-pyyntöjen lähettäminen XHR:llä

Seuraava toimii. Saat virheilmoituksen, mutta pyyntö lähtee. Voit varmistaa selaimesi kehittäjätyökaluilla tai, mikä vielä parempaa, määrittää selaimen ja verkkopalvelimen välille välityspalvelimen, kuten BurpSuiten, jotta näet, mitä tapahtuu.

let xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.open('GET', 'http://b.local/')
xhr.send()

👍 Sallittu: Evästeitä sisältävien GET-, HEAD- ja POST-pyyntöjen lähettäminen Fetch-API:lla

Fetch käyttö toimii samalla tavalla.

fetch('http://b.local/', { method: 'POST', credentials: 'include' })

Ei sallittu: Lue XHR vastaus

Kun käytät XHR:ää tai Fetchiä HTTP-pyynnön lähettämiseen vierasta alkuperää olevalle sivustolle, et voi lukea HTTP-pyyntösi vastausta.

❌ Cross-Origin-pyyntö estetty: Sama alkuperäkäytäntö estää etäresurssin lukemisen osoitteessa http://b.local/. (Syy: CORS-otsikko "Access-Control-Allow-Origin" puuttuu).

Katsotaan kohta, mitä Access-Control-Allow-Origin tarkoittaa.

Ei sallittu: PUT-, PATCH-, DELETE- jne. pyyntöjen lähettäminen

Vain tietyt HTTP-verbit ovat sallittuja oletuksena (GET, POST, HEAD ja OPTIONS).

fetch('http://b.local/', {method: 'PUT', credentials: 'include'});

❌ CORS-käytäntö on estänyt haun osoitteesta http://b.local/ lähteestä 'http://a.local': vastaus esitarkastuspyyntöön (preflight request) ei läpäise kulunvalvontatarkistusta: Ei 'Access-Control-Allow-Origin' -otsikkoa pyydettyssä resurssissa. Jos läpinäkymätön vastaus palvelee tarpeitasi, aseta pyynnön tilaksi 'no-cors' noutaaksesi resurssin, kun CORS ei ole käytössä.

Tässä virheessä on kaksi mielenkiintoista osaa. Se puhuu esitarkastuspyynnöstä (preflight) ja pyyntötilasta (request mode). Palaan pian prefight-asiaan, mutta katsotaanpa ensin nopeasti pyyntötilat.

Pyyntötilat (request mode)

Verkkosovellukset voivat käyttää tiettyä pyyntötilaa estämään tarpeettoman datan vuotaminen vahingossa pyynnössä esimerkiksi asettamalla tila nimenomaisesti samaan alkuperään (same-origin) jolloin selain heittää virheen jos pyyntö yritetään tehdä vieraaseen alkuperään.

Ei sallittu: JSON-pyyntöjen lähettäminen

Vain sallittujen sisältötyyppien luetteloon lisätyt sisältötyypit ovat sallittuja. Tämä ei toimi:

fetch('http://b.local/', {
    method: 'POST', credentials: 'include', headers: {
        'Content-Type': 'application/json'
    },
});

❌ CORS-käytäntö on estänyt haun osoitteesta http://b.local/ lähteestä 'http://a.local': vastaus esitarkastuspyyntöön ei läpäise kulunvalvontatarkistusta: Ei 'Access-Control-Allow-Origin' -otsikkoa pyydettyssä resurssissa. Jos läpinäkymätön vastaus palvelee tarpeitasi, aseta pyynnön tilaksi 'no-cors' noutaaksesi resurssin, kun CORS ei ole käytössä.

WebSocketit

👍 Sallittu: WebSocket-yhteyden avaaminen vieraaseen alkuperään, siitä lukeminen ja siihen kirjoittaminen

Tämä saattaa olla yllättävää, mutta saman alkuperän käytäntö ei rajoita WebSockettteja.

☠ Turvallisuusvaikutus: Jos WebSocketteja käyttävä sovellus ei vahvista Origin-otsikkoa WebSocket-kättelyssä tai ota käyttöön jotain muuta CSRF-suojausmekanismia, haitallinen verkkosivusto voi avata WebSocket-yhteyden ja käyttää sitä selaimen käyttäjänä.

Yhteenveto

SOP on selainturvallisuuden perusta. Se on vanha, eikä se ole täydellinen, joten kehittäjien on otettava huomioon sen puutteet sovelluksia suunnitellessaan.

Yleensä kirjoittaminen, kuten vaikkapa POST-pyyntöjen lähettäminen vieraalle sivustolle, on sallittua, mutta lukeminen, kuten vaikkapa sen POST-pyynnön vastauksen tarkastelu, on tyypillisesti kiellettyä.

Kehittäjät voivat osittain lieventää samaa alkuperää koskevaa käytäntöä CORS-otsikoiden (Cross-Origin Resource Sharing) avulla, mutta heidän tulee tehdä se varoen ja välttää CORS:ia kokonaan, jos mahdollista.

Saman alkuperän käytäntöä voidaan myös tiukentaa uudemmissa selaimissa CORP:n (Cross-Origin Resource Policy) ja COOP:n (Cross-Origin Opener Policy) avulla.

Lopuksi, ja hieman yllättäen, WebSocketit eivät ole lainkaan suojattu SOP:lla. Tällä voi olla yllättäviä ja epämiellyttäviä vaikutuksia, jos et ole varovainen niiden toteuttamisessa.

Tutustutaan seuraavassa moduulissa CORS:iin.

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ä.