Přejít k navigační liště

Zdroják » JavaScript » Cross-Site XMLHttpRequest

Cross-Site XMLHttpRequest

Cross-Site XMLHttpRequest nebo též Cross-Origin Resource Sharing (CORS) je nová a zatím nepříliš rozšířená technika, která může pomoci řešit problémy s přístupem k datům třetích stran z webových aplikací. Pokud potřebujete do své aplikace získat data odjinud, může se hodit i vám. Blíže si CORS představíme v článku.

JavaScript je nejoblíbenějším programovacím jazykem webových aplikací a ačkoliv se jeho podpora v prohlížečích posunula v posledních letech o značný kus dál, některé jeho vlastnosti mohou představovat významná omezení. Jedním z nejvýraznějších, na které dříve či později narazí většina programátorů, se jmenuje otázka stejného původu.

Zkoumání stejného původu je bezpečnostní schéma, které by mělo zajišťovat, že v rámci stránky (nechť je to pro potřeby tohoto článku totéž, co webová aplikace) je možné spustit pouze skripty, které pocházejí ze stejného zdroje. Pod stejným zdrojem se myslí doménová adresa serveru, ze kterého jsou (dodatečně) stahovaná data. Data – obecně jakákoliv – mohou být z jiného adresáře, ale musí sdílet celé doménové jméno daného WWW serveru; porušením tohoto pravidla je použití poddomény, jiného protokolu (např. HTTPS namísto HTTP) i jiného portu webového serveru. Kostra tohoto schématu pochází z doby, kdy skript v rámci stránky typicky prováděl manipulaci s uživatelskými daty a odesílal je formuláři na serveru, který se postaral o jejich zpracování. Omezení pak především mělo zabránit tomu, aby bylo možné do stránky vložit cizí kód, který by prováděl s uživatelskými daty něco nepěkného.

Doba pokročila a v dnešní době lze v JavaScriptu vytvořit a provozovat značně komplexní aplikace; u těch nejsložitějších je již člověk často na vážkách, zda jde skutečně ještě o webovou aplikaci. Velmi často je potřeba zobrazovat a pracovat s daty z jiných datových zdrojů, než je samotný webový server. Mnoho služeb dnes poskytuje zdokumentované API, které je možné využít pro získání, příp. i modifikaci dat. S těmito daty je pak potřeba pracovat v naší aplikaci. Při použití asynchronního zpracování dotazů pomocí techniky XMLHttpRequest (nebo její sesterské ActiveXObject("Microsoft.XMLHTTP")) máme problém.

Chci data odjinud!

Na každý problém existuje minimálně jedno jednoduché, elegantní a nesprávné řešení. Nejjednodušší a dle mých zkušeností i nejpoužívanější řešení našeho problému spočívá v tom, že si v rámci serverové části aplikace, např. v rámci interního API, vytvoříme vlastní proxy skript, kterému jako parametr dáme URL požadovaných dat, on se postará o jejich získání z původního zdroje a nám je vrátí, jako by to byla jeho odpověď. Takový skript často může řešit částečnou nebo úplnou transformaci dat do vhodné podoby, jejich spojení s jinými daty apod.

Někdy ovšem není vhodné pracovat s takovým proxy skriptem – třeba pokud chceme vytvořit aplikaci, do níž si uživatel může jako datový zdroj přidat jakoukoliv dostupnou službu, která nemusí být přístupná ze sítě, v níž se nachází server. V některých případech dokonce není možné použít proxy skript, prostě proto, že není možné modifikovat serverovou část aplikace. Problém se také objeví ve chvíli, kdy se klientský kód má spouštět i v lokálním režimu, tj. při načtení stránky z filesystému; pak vůbec žádná serverová strana neexistuje, a přesto je potřeba získat data z externích zdrojů.

Nechci slyšet, že to nejde

Když to nejde na serverové straně aplikace, musí to jít jinde. Částečné řešení může představovat JSON-P nebo použití elementu <iframe>. Hlavní nevýhoda tohoto postupu je v tom, že na zpracování kódu v klientské aplikaci spuštěné z vašeho serveru musel myslet tvůrce služby, která poskytuje data. Pokud se tak nestalo, máte smůlu; např. ačkoliv prohlížeč dokáže bez problémů zobrazit data z veřejné URL, vy se k nim v klientském kódu nedostanete.

Co na to W3C

Na další (bohužel opět pouze částečné) řešení jsem narazil nedávno, když jsem řešil problém s přístupem ke službám z lokálně spouštěného kódu, tedy bez serverové části aplikace. Jedná se o poměrně čerstvý kousek z dílny konsorcia W3C, a sice o specifikaci Cross-Origin Resource Sharing. Jde o návrh (working draft) pracovní skupiny pro webové aplikace, jehož aktuálně dostupná verze je z 16. června tohoto roku. Specifikace je zajímavá především tím, že ji implementuje Firefox 3.5. Protože můj problém se týkal aplikace napsané v XULu, začal jsem vše zkoumat trochu podrobněji.

Myšlenka je jednoduchá a stejně jako v případě JSON-P se neobejde bez zásahu na straně poskytovatele dat – v tomto případě jde o nastavení hlaviček, které vrací webový server. Specifikace totiž definuje, že pokud server poskytující data (tj. v našem případě třetí strana) v hlavičkách odpovědi uvede některou platnou direktivu Access-Control-*, klientský kód smí získaná data použít k dalšímu zpracování. Hlaviček je momentálně v návrhu osm, tou nejdůležitější z nich je Access-Control-Allow-Origin, která řídí původ (tj. serverovou stranu) dotazující se strany. Pokud tedy server webapp.example­.com vrátí na konkrétní dotaz odpověď s hlavičkou Access-Control-Allow-Origin: http://my-domain.example.com, znamená to, že moje aplikace smí pracovat s daty získanými z webapp.exam­ple.com pomocí  XMLHttpRequest.

Specifikace upravuje i složitější případy užití, umožňuje omezit např. časovou platnost dotazu nebo HTTP metody (resp. REST verb(s)). Specifikace také operuje s přípravným dotazem (preflight request), který může webová aplikace použít ke zjištění, jestli jsou pro ni požadovaná data dostupná. V mém případě stačilo pro účely testování přidat do hlaviček web serveru daného virtuálního adresáře přidat direktivu Access-Control-Allow-Origin: * a aplikace funguje korektně z jakéhokoliv lokálního i intranetového zdroje.

Ačkoliv se nejedná o převratnou novinku, nabízí specifikace řešení jednoho z problémů, se kterými se programátor webových aplikací může občas potkat. Skutečnost, že se první funkční implementace objevila tak brzy, naznačuje, že se nejedná o čistě akademický problém. Můžeme jen doufat, že další prohlížeče specifikaci v brzké době implementují také.

K dalšímu čtení

Jak pohlížíte na techniku, popsanou v článku?

Komentáře

Subscribe
Upozornit na
guest
43 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
huh

Javascript je jediný jazyk, který běží v prohlížeči. Kdybych si mohl vybrat, tak bych používal radši skoro cokoli jinýho, snad kromě APLu.

petrik

Javascript nie je jediny.

Ped

On myslel programovaci jazyk. :)

Ped

Ja sarkasticky narazel na kvalitu VBscriptu. I kdyz to ode mne neni fer, nakonec za to co vznikne je odpovedny hlavne tvurce, ale nekdy mi to prijde ze VB vyslovene taky-programatory pritahuje. No dobre, byl to laciny a trapny pokus o vtip, uznavam. (mne se ostatne casto zveda zaludek i z nekterych jscriptu ktere na webu vidim, takze prast jako uhod)

Michal Augustýn

Přijde mi to trošku zvláštní (ale třeba mě někdo přesvědčí o opaku), ale znamená to, že request prohlížeč provede VŽDY, ale až teprve podle přítomnosti hlavičky rozhodne, jestli data předat dále?
Ale možnost určit, kdo smí využívat mé služby, se zdá docela rozumná (ačkoliv je to je „dobrá vůle“ prohlížeče, jestli předá data dále)…

soudruh

Také jsem to takto špatně napřed pochopil, ale zde jde o hlavičku kterou odešle webový server scriptu, ne webový server dat.

ah01

Dnes ráno jsem něco hledal na MSDN a náhodou při tom narazil na XDomainRequest Object a pak na článek Client-side Cross-domain Security.

Neměl jsem moc čas to studovat, ale pominuli název objektu, tak princip mi přijde na první pohled dost podobný, nebo ne?

brut4r

Je to stejne dokonce to vyzaduje tu hlavicku Access-Control-Allow-Origin, a podle textu je to postavene na tom samem standardu. Takze IE8 uz to umi taky ;)

Borek Bernard

Nerozumím, jaký problém se za mě prohlížeče snaží vyřešit tím, že JavaScriptu znepřístupní data z cizích domén. Co by se stalo, kdyby hypoteticky všechny prohlížeče zítra přestaly kontrolovat původ?

ah01

Předpokládejme, že protože by se jednalo o regulérní požadavek na danou doménu, posílalo by se v hlavičce i cookie, tedy i SessionID a podobně. No, a pokud by byl dotyčný návštěvník na cílové aplikaci přihlášený…

Mohl bych pak na stránku umístit kód, který by uměl u návštěvníka přihlášeného na Gmailu stáhnout veškerou jeho poštu.

Navíc, stejná Same Origin Policy se týká i přístupu k iframu z jiné domény. Takže bych mohl na stránku umístit skrytý iframe, nahrát do něj Gmail a doslova si s ním dělat co chci.

Borek Bernard

Proč by se s každým požadavkem kamkoliv měly posílat i cookies? Např. při navštívení twitter.com se v hlavním HTTP požadavku pošle auth_token cookie, ale při požadavcích na obrázky z domény xyz.twimg.com se autentizační cookie přirozeně neposílá.

Nejsem žádný bezpečnostní expert, ale zdá se mi, že popisujete běžný XSS, který funguje nezávisle na kontrolování Same Origin. Nebo se pletu?

Envel

Zkus si představit, že jsi přihlášen do webové aplikace A, která obsahuje na určité stránce pro tebe citlivá data. Co by se stalo, kdybys vstoupil na stránku, která by posílala request aplikaci A s žádostí o tvá citlivá data? Jsi-li přihlášen, podle HTTP protokolu bys odeslal i cookie a skript by získal tvé údaje. To samozřejmě nikdo nechce. Samozřejmě mluvíme o možnosti, že jsi do původní aplikace přihlášen.

Jinak cross-site request pouze zjednodušuje některé způsoby zneužití XSS chyb.

Borek Bernard

Ok, ve Firefoxu jsem přihlášen v aplikaci A na doméně a.example.com, ta pomocí JavaScriptu načítá data z URL http://a.example.com/api.php a tento PHP skript kontroluje uživatele pomocí cookie ‚SID‘. Na nové záložce otevřu web http://mallory.example.com/index.php – web útočníka. Ten chce získat citlivá data z URL http://a.example.com/api.php – v současnosti je přímý request na toto URL pomocí JavaScriptu nemožný, kdyby však možný byl a neposílaly se cookies (stejně jako se tak neděje třeba při načítání obrázků z jiné domény), jak by byly moje data ohroženy? Skript api.php by zjistil, že součástí požadavku není SID cookie a vrátil by nějakou chybu, jak se ostatně stane, když web útočníka nezaútočí přes JavaScript, ale třeba přes serverové PHP volání.

Patrně mi uniká nějaký „detail“ s posíláním cookies – zdá se, že ty i Adam jaksi předpokládáte, že se cookies pošlou, a já si nejsem jistý, z čeho tento předpoklad vychází. V příkladu, co jsem uvedl, nevidím žádný důvod, proč by se měly posílat a celý systém tak udělat zranitelný.

Borek Bernard

Popravdě mi pojmy jako „útočící obránce“ nebo prohození záškodnosti a.example.com a mallory.example.com do případu mnoho světla nevnášejí :) Pokud vás ale chápu dobře, říkáte, že bez Same Origin Policy bych si mohl do své aplikace natáhnout skript z cizí domény a spustit ho pomocí eval(), čímž by se mi mohla stát řada nepříjemných věcí. To souhlasím, ale současně musím něco takového čekat, když dělám eval() na cizím nedůvěryhodném kódu, ne? Je to asi jako ve Windows spustit EXE soubor – „hodné“ programy mi pomůžou, ty zlé mi můžou ukrást data, naformátovat disk a podobně. Je na mě, jestli kód spustím nebo ne, nevidím v tom principiální problém.

Asi by pomohlo, kdyby někdo popsal nějaký hypotetický útok při absenci Same Origin Policy od začátku do konce. Třeba jak bych kvůli tomu mohl přijít o emaily v GMailu.

Daniel Steigerwald

Borku, takových požadavků lze udělat více druhů, problém je 1) objem dat a 2) bezpečnost. Požadavkem na obrázek získáš tak maximálně jeho rozměry (v IE, Canvas by jej naparsovat uměl). V připojeném CSS stylu, v background-image např. už by se něco dat vešlo :) Avšak do stylů lze vložit script, tak fungoval myspace Samy worm. Připojit lze i script přímo, tak funguje JSON-P, avšak načtení cizího scriptu je krajně nebezpečné. Zbývá tedy XHR a IFrame. XHR popisují ostatní komentující. Dva iframe z různých domén se sice navzájem poškodit nemohou, komunikace však možná je. 1) zápisem do location hash 2) hack window.name První případ se dříve často používal, nejedná se totiž o neopravenou bezpečností chybu. Bohužel někdo hooodně moc hloupý v MS rozhodl, že prohlížeč IE7 bude mít skvělou vlastnost → klikne při změně url. Tohle v JS nejde vypnout, jedině obejít tak, že pro každou správu se vytvoří nový iframe, a location nastaví ještě před vložením do stránky.. to je ale děsně neefektivní způsob. Hack pro window.name používá např. Dojo. Je to divné, ale takto lze přenést až deset mega v jedné zprávě. V praxi se tedy musí použít způsob jiný. Odpověď na tvou otázku tedy zní, pokud by někoho napadlo při návrhu XHR volitelně neposílat cookie, pak by nebylo nutné XHR jakkoliv omezovat.

Martin Straka

V článku je to myslím také nepřesně podané. Jde hlavně o (ne)přístup k datům z jiných domén a jedná se možná o nejdůležitější koncept v oblasti bezpečnosti použitý v prohlížeči. Izoluje aplikace od sebe navzájem.

Kdyby to prohlížeče nedělaly, tak by například vůbec nefungovala obrana proti XSRF (CSRF) založená na tokenu ve stránce:

http://php.vrana.cz/…-forgery.php http://zdrojak.root.cz/…k-se-branit/

protože by si ho útočící stránka jednoduše přečetla:) a dalších příkladů se dá uvést spousta.

Občas v tom prohlížeče mají chybu (taky jsem jednu reportoval do Firefoxu) a to je pak nepříjemné a z hlediska provozovatele aplikace se proti tomu špatně brání.

Jako dobrý zdroj na studium bych doporučil Browser security handbook od Michala Zalewskiho:

http://code.google.com/…c/wiki/Part2#…

Borek Bernard

Dík za tip na Browser sercurity handbook, počtu si, až budu mít čas.

Jakub Vrána

Jak už bylo uvedeno, tak se tím brání získávání citlivých dat. Ve většině případů by asi stačilo, aby se na cizí doménu neposílaly cookies a HTTP autentizace, ale zdaleka ne vždy. Někdy je zcela v pořádku chránit stránku jen podle IP adresy uživatele, chránit před CSRF je potřeba třeba i anketu pro nepřihlášené uživatele. Pro tyto dva případy by cross-site request byl nebezpečný, i kdyby se neposílaly cookies a HTTP autentizace.

Borek Bernard

Je tedy obrana proti CSRF jedním z hlavních use-casů, kde by se absence kontroly Same Origin projevila nejvíce? Čekal jsem něco očividnějšího a u příspěvků výše, které mluví o jakémsi session hijacking, pokud používám správný termín, si přesnou anatomii útoku pořád nedokážu představit.

Jakub Vrána

Jde o to, že při absenci same-origin by útočník mohl získat všechna data a provést všechny operace na všech webech jménem uživatele, který přijde k němu na stránky.

Borek Bernard

Viz http://zdrojak.root.cz/…nazory/5218/. Tvůj výrok podle mě platí pouze za předpokladu, že se s JavaScript x-site requesty posílají i cookies. Lze to automaticky předpokládat? Patrně mi někde něco uniká…

Jakub Vrána

Jak jsem psal ve svém původním příspěvku – pokud by veškeré autorizace byly založeny na cookies, tak by pouhé neposílání cookies problém vyřešilo. Ale někdy je autorizace založena třeba jen na IP adrese nebo v případě anket pro nepřihlášené uživatele ani na tom. Navíc cookie nemusí udržovat jen informaci o přihlášení, takže neposlání cookies by mohlo způsobit nevhodnou reakci webu.

Martin

Nejde pouze o javascript x-site requesty. Další příklad aplikování same-origin policy jsou například otevřená okna, framy/iframy, nadřízená okna a jejich (ne)přístup z javascriptu plus nespočet dalším možností jak se k DOM stránky dostat.

Pokud nejsou ze stejné domény tak také nemají přístup k této stránce, nemohou z ní číst, ani ji modifikovat. Pokud je stránka v jiném okně/framu/iframu ze stejné domény (pominul jsem další věci, jako stejný port, protokol) může ji javascript libovolně číst nebo modifikovat.

Například díky same origin policy si nemůžu číst poštu v okně které není moje (jiná doména) když by obět náhodou byla přihlášená:

  seknam = window.open('http://email.seknam.cz/folderScreen');

  ...

  alert(seknam.document.getElementById('message1').innerHTML);
Daniel Steigerwald

Jen doplním, dva iframe z různých domén si mohou zapisovat do location hash. Kdyby někoho v MS nenapadlo, že IE7 má při každé změně location klikat, mohly jsme dávno používat tuhle techniku zapouzdřenou v JS knihovnách. Momentálně se používá window name, ale této technice moc nevěřím.., co když někoho v MS napadne to „opravit“..

rtgfv

blokovat ajaxovej request na cizi domenu je naprosta kokotina, mimochodem to drive dokonce i fungovalo nevim jestli v ie6 nebo ve ff 1.5.. kdyz si bude chtit utocnik odeslat javascriptem citliva data ma mnoho jinych zpusobu nez pouzit ajax

NG

Zdravim, možná teď některé z vás překvapím, ale cross-site requesty fungují ve všech dnes používaných prohlížečích a není k tomu potřeba měnit jakékoliv nastavení prohlížeče. Stačí jen místo XMLHttpRequest použít jiný postup. Na základě této „finty“ fungují všechny JavaScript API od Googlu a jiných. Jinak by ani nešly dělat mash-up aplikace, které průběžně (AJAXově, chcete-li) dostávají data z jiné domény. Přijde mi to, že je to veřejné tajemství, o kterém se nemluví. Že by to někdo v budoucnu omezil je dost nepravděpodobné, protože by přestalo fungovat spoustu věcí a to si nikdo nedovolí udělat.

Martin Malý

Myslím že to pro nikoho překvapením nebude. Klíčové slovo je JSON-P, v článku je odkaz a na odkazu vysvětlení – to je ta finta, ne? „Veřejné tajemství, o kterém se moc nemluví“ není nic jiného než skriptem vytvořený DOM element SCRIPT se SRC nastaveným na JSON-P datový zdroj.

NG

Sorry, přehlédl jsem a ano, to je ta finta. Nicméně to, že tento způsob a hlavně jeho uplné využití není moc známe je zřejmé jak z minimální zmínky v článku, tak z komentářů v této diskuzi. A neznají to ani „profesionálové“, neb vytřeštěné oči zaměstnanců několika velkých integrátorů v ČR jsem už bohužel viděl. Obecně se totiž povědomí o této problematice pohybuje ve dvou extrémech – všechno je možné (pak údiv nad tím, že si IFRAME nebo okno z jiné domény s parentem nepopovída) a nic není možné (pak zase údiv nad tím, že to vlastně jde).

Navíc věta: „Hlavní nevýhoda tohoto postupu je v tom, že na zpracování kódu v klientské aplikaci spuštěné z vašeho serveru musel myslet tvůrce služby, která poskytuje data.“

by se měla zarámovat, ale v opačném smyslu, protože když na to tvůrce služby myslel, tak vytvořil nějaké API a to je možno považovat za kontrakt mezi poskytovatelem služby a jejím uživatelem. V opačném případě chcete konzumovat něco, co třeba druhá strana ani nepředpokládala, že někdo použije, tj. může to kdykoliv nekompatibilně změnit, zrušit, atp.

Daniel Steigerwald

JSON-P je díra jak hrom. Můžu jej použít, pouze pokud je poskytovatel velmi důvěryhodný. A pokud někdo hackne jej, mám smůlu. Opravdu bezpečné čistě javascriptové jsou pouze dva způsoby, které jsem popsal výše.

NG

„JSON-P je díra jak hrom.“ je podle mne hodně silné tvrzení. Ano, je problém v tom, že když se z hodného poskytovatele stane zlý, což se může stát hacknutím jeho serveru. Když se tak stane, tak je na mne, jestli to vyhodnotím jako důvod pro změnu poskytovatele. Z opačného pohledu stejně tak dobře může někdo hacknout i můj server. BTW víte, kolik úspěšných útoků zaměřených na JSON-P proběhlo v poslední době?

NG

Dle odstavců „Chci data odjinud!“ a „Nechci slyšet, že to nejde“ jsem tak nějak nabyl dojmu, že se článek zmiňuje i o tom, jak dostat data z jiné domény. Textu je tam na toto téma dost.

Když tak nad tím přemýšlím, tak místo těch dvou zmíněných odstavců tam mohla být jen krátká zmínka o proxy (možná pro většinu relativně složité/kompli­kované); IFRAME snad ani nezmiňovat, protože to se s cross-site vůbec nerýmuje; a spíš popsat způsob, jak to jde udělat i se stávajícími prohlížeči (a třeba i včetně poznámky, že jisté bezpečnostní rizika tam jsou).

Ad API pro web, desktop a server) V tomto případě jsem uvažoval jen konzumenta v podobě webu – asi proto, že ten článek je o cross-site, který desktop ani server nezajímá.

NG

Ach jo… Původně jsem napsal, že „Obecně cross-site funguje a že stačí jen místo XMLHttpRequest použít jiný postup.“ Tím jsem reagoval na dva odstavce v článku. Od teď díky Vám vím, že musím vždy reagovat na celý článek. Děkuji.

_

Pokiaľ viem, tak doteraz sa nedalo z hľadiska bezpečnosti pracovať v JavaScripte s cudzím HTML kódom.
Dalo sa pracovať s cudzím JavaScriptom, CSS, obrázkami, …, no nie s cudzím HTML kódom.
Ten spôsob uvedený v článku by mal asi umožniť aj toto, pokiaľ to server povolí.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.