Skip to content
Language:hu Author:Laborci Gergely
web-security

Same-origin policy és CORS

Célok

A lecke végére meg tudod mondani, mikor azonos két URL eredete, mit korlátoz és mit enged a same-origin policy, hogyan működik a CORS és a preflight, és miért nem hozzáférés-védelem a CORS.

Miért kell elkülöníteni az oldalakat?

A hallgató egyszerre megnyitja a Kurzuspontot és egy idegen weboldalt. Ha az idegen oldal JavaScriptje akármit kérhetne a Kurzuspont nevében, és elolvashatná a választ, a bejelentkezett hallgató személyes adatai a támadó kezébe kerülnének: a böngésző ugyanis a cookie-t is csatolná. A böngésző ezért szabályt tart fenn: a különböző eredetű oldalak egymás adatait alapesetben nem olvashatják. Ez a same-origin policy (azonos eredet elve).

Mit jelent az eredet?

Az eredet (origin) a séma, a hosztnév és a port együttese. Az MDN példája szerint a http://store.company.com/dir/page.html címhez viszonyítva:

Másik URL Azonos eredet? Miért?
http://store.company.com/dir2/other.html igen csak az útvonal különbözik
https://store.company.com/page.html nem más a séma
http://store.company.com:81/dir/page.html nem más a port (a http alapértelmezett portja 80)
http://news.company.com/dir/page.html nem más a hosztnév

Az útvonal tehát nem része az eredetnek. (A cookie-k az MDN szerint más tartomány- és útvonal-alapú értelmezést követnek, nem az eredetet.) Hasonlóan eredetenként különülnek el a localStorage és az IndexedDB tartalmai is (3.6).

flowchart LR
    A[kurzuspont.example oldala] -->|azonos eredet| P[kurzuspont.example API]
    A -.->|kereszt-eredet: CORS kell az olvasáshoz| Q[api.kurzuspont.example]
    X[idegen.example oldala] -.->|olvasás korlátozott| P

Mit korlátoz és mit enged?

Az MDN összefoglalója szerint:

  • Tiltott (alapesetben): egy oldal szkriptje nem olvashatja egy másik eredet válaszát (fetch() válasz), és nem fér hozzá a másik eredet tárolt adataihoz.
  • Megengedett: kereszt-eredetű írás (hivatkozások, átirányítások, űrlapok elküldése), valamint beágyazás (<script src>, <img>, <iframe>, stílusok), amelyek során a böngésző kérést küld, de a szkript nem olvashatja az eredményt.

Ez nagyon fontos különbség: a same-origin policy nem azt mondja, hogy a böngészőből nem indulhat kérés más eredet felé. Azt mondja, hogy a küldő oldal JavaScriptje általában nem olvashatja a választ. A szerver ettől még megkaphatja a kérést, és akár mellékhatás is történhet (8.4).

Mit tesz a CORS?

A CORS (kereszt-eredetű erőforrás-megosztás) HTTP-fejlécekkel működő mechanizmus, amellyel a szerver megmondhatja, mely más eredetek tölthetik be az erőforrásait, illetve olvashatják a válaszát a böngészőben. Működés:

  1. A böngésző a kereszt-eredetű kérésben Origin fejlécet küld.
  2. A szerver az Access-Control-Allow-Origin válaszfejlécben megnevezi az engedélyezett eredetet.
  3. Ha a válasz engedélyezi a kérő eredetét, a böngésző átadja a választ a szkriptnek; ha nem, blokkolja.

Kipróbálva egy kipróbáló szerverrel, amely csak a https://kurzuspont.example eredetet engedélyezi:

$ curl -i localhost:8087/api -H 'Origin: https://kurzuspont.example'
HTTP/1.1 200 OK
Vary: Origin
Access-Control-Allow-Origin: https://kurzuspont.example
{"kurzusok":["webprog"]}

$ curl -i localhost:8087/api -H 'Origin: https://idegen.example'
HTTP/1.1 200 OK
{"kurzusok":["webprog"]}

A második kérésre a szerver ugyanúgy válaszolt (200, törzzsel), csak az Access-Control-Allow-Origin fejléc hiányzik. A böngésző emiatt nem adná át a választ az idegen oldal szkriptjének. A curl viszont nem böngésző: nem érvényesíti a CORS-t, ezért ugyanazt a törzset látja. A CORS tehát böngészős olvasási korlát, nem hozzáférés-védelem.

A Vary: Origin jelzi a gyorsítótáraknak, hogy a válasz az Origin értékétől függ (az MDN javasolja dinamikus eredetnél).

Preflight

Az MDN szerint egyszerű kérés az, amely GET, HEAD vagy POST metódusú, csak biztonságosnak minősített fejléceket használ, és a Content-Type értéke application/x-www-form-urlencoded, multipart/form-data vagy text/plain. A többi (például Content-Type: application/json vagy egyedi fejléc) esetén a böngésző előbb egy preflight OPTIONS kérést küld, és csak az engedélyek alapján folytatja a tényleges kérést.

$ curl -i -X OPTIONS localhost:8087/api -H 'Origin: https://kurzuspont.example' \
    -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: content-type'
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://kurzuspont.example
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600
sequenceDiagram
    participant B as Böngésző
    participant A as API
    B->>A: OPTIONS + Origin + tervezett metódus és fejléc
    A-->>B: Engedélyezett eredet, metódus, fejlécek
    B->>A: Tényleges API-kérés
    A-->>B: Válasz + CORS-fejlécek

Az Access-Control-Max-Age megmondja, mennyi ideig tárolható a preflight eredménye (az MDN szerint alapértelmezés szerint 5 másodperc). Ha az idegen eredetre a preflight nem ad engedélyt (a fenti szerver ilyenkor üres 204 választ ad, Access-Control-Allow-* nélkül), a böngésző a tényleges kérést el sem küldi.

Alapértelmezés szerint a kereszt-eredetű kérések nem küldenek hitelesítő adatot (cookie-t). Ha mégis szükséges, a kliensnek ezt kérnie kell (credentials: "include"), a szervernek pedig Access-Control-Allow-Credentials: true választ kell adnia. Az MDN szerint ilyenkor a * helyettesítő érték nem használható az Access-Control-Allow-Origin fejlécben: kifejezett eredetet kell megadni. A cookie-k saját SameSite szabályai ettől külön réteget alkotnak (7.2).

Mire nem való a CORS?

Gyakori hiba, hogy a fejlesztők a CORS-hibát „megoldják” egy * eredet beállításával. Ez csak a böngészős olvasási korlátot oldja fel mindenkinek; ha a válasz személyes adatot tartalmaz, a hozzáférés-védelmet nem a CORS-nak kell adnia.

Végigvezetett példa: a Kurzuspont külön API-tartománya

A Kurzuspont felülete a https://kurzuspont.example címen fut, az API a https://api.kurzuspont.example címen. A két cím eltérő hosztnév, tehát eltérő eredet.

Helyzet Mi történik?
A felület JSON-t kér az API-tól Preflight (JSON Content-Type), amelyre az API engedélyezi a https://kurzuspont.example eredetet.
Egy idegen oldal ugyanezt kérné A preflightra nem kap engedélyt, a böngésző nem küldi el a tényleges kérést.
A felület cookie-t is küldene credentials: "include", az API pontos eredetet és Allow-Credentials: true fejlécet ad.
Egy szkript curl-lel hívja az API-t A CORS nem érvényesül; az API-nak hitelesítéssel és jogosultsággal kell védekeznie.

Gyakori tévhitek

Állítás Pontosítás
„A CORS megvédi az API-t a nem kívánt kliensektől.” A böngészőben a válasz olvasását szabályozza; szerveroldali hozzáférésvédelem is kell.
„Másik útvonal már másik eredet.” Az útvonal nem része az eredetnek.
„A CORS-hiba azt jelenti, hogy a kérés nem ment el.” Egyszerű kérésnél a kérés elmehet, csak a válasz nem olvasható.
„Minden kereszt-eredetű kérés előtt van OPTIONS.” Csak a nem egyszerű kérések előtt.
„A * mindig jó megoldás.” Cookie-s kérésnél nem használható, és személyes adatnál veszélyes.

Röviden

  • Az eredet = séma + hosztnév + port; az útvonal nem része.
  • A same-origin policy a kereszt-eredetű olvasást korlátozza, az írást és a beágyazást engedi.
  • A CORS a szerver Access-Control-* fejléceivel engedélyez kereszt-eredetű olvasást; nem egyszerű kérés előtt preflight van.
  • A CORS böngészős mechanizmus: curl-re nem hat, jogosultság-ellenőrzést nem pótol.

Ellenőrző kérdések

  1. Azonos eredetű-e a https://kurzuspont.example és a https://kurzuspont.example:8443?
  2. Mit enged és mit tilt a same-origin policy?
  3. Mikor küld a böngésző preflight kérést?
  4. Miért nem védi az API-t a CORS egy közvetlenül futtatott szkripttel szemben?

Tanulási források

Prerequisites (2)

  • HTTP
  • JavaScript

Teaches (2)

  • same-origin policy
  • CORS