डेवलपर्स CORS को नहीं समझते (2019)
(fosterelli.co)- Zoom के local webserver की कमजोरी ने दिखाया कि जब कई web developers CORS कैसे काम करता है इसे गलत समझते हैं, तो security boundary आसानी से टूट सकती है
- Zoom ने
localhost:19421local server से बात करते समय AJAX की बजाय image size के जरिए status code पहुँचाया, और इसे CORS से बचने के लिए किया गया workaround implementation माना गया - Chrome localhost webserver पर भी CORS headers लागू करता है, और अलग-अलग localhost ports पर चल रहे frontend और backend के बीच communication भी browser में supported है
- ज़्यादा सुरक्षित डिज़ाइन यह है कि local server एक REST API दे और
Access-Control-Allow-Originसेट करके केवल zoom.us के JavaScript को access की अनुमति दे - Same-origin policy को bypass करने पर code चल तो सकता है, लेकिन local server की privileged functionality इंटरनेट की सभी websites के सामने उजागर हो सकती है
Zoom local webserver द्वारा बनाया गया CORS bypass
- full-stack consulting के काम के दौरान अलग-अलग आकार और industries के developers के साथ काम करते हुए, बार-बार यह दिखा कि web developers CORS को नहीं समझते
- हाल की Zoom vulnerability में security researcher Jonathan Leitschuh ने पाया कि Zoom user machine पर
http://localhost:19421webserver चलाता है- जब user Zoom link खोलता है, तो Zoom website localhost webserver को request भेजकर native Zoom app चलाती है
- सामान्य AJAX request की जगह local Zoom webserver से image load की जाती थी, और image के अलग-अलग sizes से server errors और status codes दिखाए जाते थे
- यह समझ गलत थी कि browser localhost server की CORS policy को नज़रअंदाज़ करता है; Chrome localhost webserver के CORS headers का सम्मान करता है
- Create React App frontend और backend API को अलग-अलग localhost ports पर चलाने पर भी cross-origin requests होती हैं, और यह सभी browsers में supported है
- AJAX requests block होने के बाद लगता है कि Zoom ने image hack के जरिए CORS को bypass किया
- नतीजे में सिर्फ Zoom website ही नहीं, बल्कि इंटरनेट की दूसरी websites भी native client actions trigger कर सकती थीं और response तक access पा सकती थीं
सुरक्षित विकल्प और बाकी बची UX समस्या
- सुरक्षित implementation यह है कि
localhost:19421का webserver REST API implement करे औरAccess-Control-Allow-Originheader का मानhttps://zoom.usसेट करे- इससे केवल zoom.us domain पर चल रहा JavaScript ही localhost webserver से communicate कर सकेगा
- zoom.us background में Zoom meeting अपने-आप खुलने से रोकने के लिए iframe rendering block करने वाला Content Security Policy header भी रख सकता है
- फिर भी यह समस्या बनी रहती है कि कोई भी page browser को zoom.us meeting link पर redirect कर सकता है
- लेकिन यह software vulnerability से ज़्यादा Zoom द्वारा चुने गए user experience का मामला है
- Zoom user की उस expectation को तोड़ रहा है कि link क्लिक करने पर camera और microphone अचानक किसी अनजान व्यक्ति के लिए चालू नहीं हो जाएंगे
- अगर browser default popup को UX कारणों से टालना हो, तो app के अंदर popup दिखाया जा सकता है, और Google Meet इस तरीके का अच्छा उपयोग करता है
- localhost पर webserver चलाना अपने-आप में एक जोखिमभरा प्रयास है, और खासकर software installation जैसी privileged functionality इंटरनेट की सभी websites को नहीं देनी चाहिए
- CORS ऐसे हालात को सुरक्षित तरीके से संभालने के लिए है, इसलिए इसे bypass नहीं करना चाहिए
सिर्फ Zoom की गलती नहीं, CORS को लेकर व्यापक भ्रम
- यह पक्का नहीं है कि Zoom ने सचमुच CORS को न समझने की वजह से यह तरीका चुना
- Reddit के lerunicorn का मानना है कि Firefox secure origin से insecure origin तक XHR को block कर सकता है
- लेकिन Firefox origin के localhost होने पर इसे support करता है
- native apps अपना अलग self-signed certificate बना सकती हैं, और browser extensions का उपयोग भी किया जा सकता है
- किसी भी स्थिति में origin filtering छोड़ देने का यह उचित कारण नहीं बनता
- CORS को लेकर भ्रम सिर्फ Zoom तक सीमित नहीं है
- Stack Overflow पर
Access-Control-Allow-Originसे जुड़े बहुत से सवाल मौजूद हैं - Express के कुछ examples ऐसे unsafe defaults सुझाते हैं जिन्हें सीधे copy करने पर application vulnerable हो सकती है
- दूसरे vendors भी Zoom जैसी उसी vulnerability का सामना कर चुके हैं
- Stack Overflow पर
- developers code को काम करते देखना चाहते हैं, लेकिन same-origin policy को पूरी तरह bypass करने पर Zoom के मामले की तरह local privileges बाहरी websites के सामने खुल जाते हैं
- CORS को लेकर भ्रम experienced developers और नए developers दोनों में दिखता है; यह साफ नहीं है कि CORS API बहुत जटिल है या CORS और CSP की शिक्षा कम है, लेकिन मौजूदा तरीका ठीक से काम नहीं कर रहा
1 टिप्पणियां
Hacker News टिप्पणियाँ
लगता है TFA भी CORS को ठीक से नहीं समझ पाया, या उसे गंभीर रूप से गलत समझाया गया है
Access-Control-Allow-Origin: https://zoom.usयह सुनिश्चित नहीं करता कि केवल zoom.us डोमेन का JavaScript ही localhost server से बात कर सके। दूसरी websites का JavaScript भी बिल्कुल उसी तरहlocalhost:19421पर request भेज सकता है। CORS किसी चीज़ को सीमित करने का नहीं, बल्कि डिफ़ॉल्ट प्रतिबंधों को ढीला करने का तंत्र है। यह header सिर्फ इतना करता है कि zoom.us पर चल रहा JavaScriptlocalhost:19421का response पढ़ सके; request तो वैसे भी जाएगी, इसलिए backend को इस तरह बनाया जाना चाहिए कि side effects न होंGET requests भेजी तो जाती हैं, लेकिन वे मूल रूप से idempotent होनी चाहिए, इसलिए अगर server सही तरह implement किया गया है तो वे side effects नहीं पैदा कर सकतीं, और GET में असली सवाल यह है कि response पढ़ा जा सकता है या नहीं। इसके उलट, non-idempotent requests जिनमें side effects हो सकते हैं, cross-origin स्थिति में असली request से पहले preflight OPTIONS request भेजती हैं, और अगर OPTIONS response में सही headers नहीं हैं, तो असली request भेजी ही नहीं जाती
CORS को लेकर गलतफ़हमियाँ इतनी व्यापक हैं और documentation भी अक्सर एक-दूसरे से टकराती है, कि यह मानना मुश्किल है कि कोई अनजान पक्ष इसे ठीक से implement करेगा। जब कोई protocol इतना व्यापक भ्रम पैदा करे, तो भले एक पक्ष सही काम कर रहा हो, दूसरे के बारे में भरोसा नहीं किया जा सकता। अगर लोगों ने अलग-अलग implementations के हिसाब से code को तब तक बदलते-बदलते चलाया है, तो फिर यह भी धुंधला हो जाता है कि गलती अपनी तरफ थी या सामने वाले की
उदाहरण के लिए,
Content-Typeअगरtext/jsonवाला POST है, तो उसे OPTIONS preflight के बिना किसी third-party host पर नहीं भेजा जा सकता, लेकिनmultipart/form-dataवाला POST अनुमत है और CORS उसे नहीं रोकता। और अगर endpointContent-Typeको सख्ती से जाँचने के बजाय JSON मानकर चलता है, तो इसका मतलब है कि कोई भी website बिना user interaction के POST भेज सकती हैकोई भी ठीक-ठाक web developer GET/HEAD/OPTIONS को state बदलने वाला नहीं बनाएगा, और meeting join करना जैसी चीज़ state change है। PUT/DELETE भी idempotent होने चाहिए। JSON या form के अलावा किसी और format वाली POST API को
Content-Typeheader जाँचना चाहिए, औरPUT/PATCH/DELETEतथा non-formContent-Typeवाली POST, preflight trigger करती हैं, इसलिए CORS की जाँच असली request server तक पहुँचने से पहले हो जाती हैसिर्फ certificate बना लेने से काम नहीं चलता; उसे machine के सभी browser trust stores में root CA certificate के रूप में install करना पड़ता है। अगर root CA की private key ठीक से सुरक्षित न हो, तो कोई भी website man-in-the-middle attack कर सकती है, इसलिए कम-से-कम name constraints चाहिए (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10). लेकिन Chrome में 2023 के v112 तक root CA पर यह काम नहीं करता था (https://alexsci.com/blog/name-non-constraint/), इसलिए एक intermediate CA जोड़कर उस पर constraints लगानी पड़ती थीं। बेशक, root CA key को तो फेंक देना ही सही है
मैंने पहले एक project में local root CA इस्तेमाल किया था जहाँ default constraints जोड़ी थीं, लेकिन उन्हें root CA में गलत जगह डाल दिया था, और सभी browsers में test भी नहीं किया था
काश ज़्यादा लोग MDN का CORS documentation पढ़ें। CORS समझने में इसने मेरी बहुत मदद की थी, और यहाँ comments देखकर पता चला कि लोग इसे इतना मुश्किल मानते हैं
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
मुश्किल सिर्फ CORS नहीं है; बहुत से developers threat model को भी ठीक से नहीं समझते
समझाने पर भी कई बार यह सहज नहीं लगता कि यह इतनी बड़ी समस्या क्यों है। खासकर backend developers को अक्सर CORS configure करना पड़ता है, लेकिन चूँकि CORS access control mechanism नहीं है, backend के नज़रिये से यह बहुत महत्वपूर्ण नहीं लगता। attacker कुछ चुरा नहीं पाएगा, ऐसा महसूस होता है, और frontend के लिए यह बस एक परेशान करने वाली रुकावट लग सकती है। यह लेख ठोस उदाहरण अच्छी तरह दिखाता है
operations engineer के तौर पर मैंने load balancer पर इसे फिर से सही किया, और कम-से-कम application अब चल तो रही है। CORS समझना मुश्किल है, लेकिन इससे भी दुखद यह है कि बहुत से developers न सिर्फ CORS जिस threat model को रोकना चाहता है उसे नहीं समझते, बल्कि पूरे web development, खासकर HTTP protocol, को भी ठीक से नहीं समझते
multipart/form-dataठीक है, लेकिन application JavaScript नहींCORS optional है, और दूसरी libraries या tools इसे बस ignore भी कर सकते हैं। CORS वास्तव में सिर्फ logged-in human users के खिलाफ XSS और CSRF रोकने में मायने रखता है; बाकी attack scenarios में इसका मतलब नहीं क्योंकि वहाँ scripts या programs वैसे भी HTTP headers forge कर लेते हैं। इसलिए लोग आखिर में सभी CORS options चालू कर देते हैं, जो सबसे बुरा मामला है क्योंकि इससे XSS और CSRF संभव हो जाते हैं
इस comment section का level सच में काफ़ी कमज़ोर लगता है, और उल्टा यह लेखक की बात को ज्यों का त्यों साबित करता है
अगर आपने CORS आने से पहले web development किया है, तो आप समझते हैं कि मूल रूप से cross-domain requests पर रोक थी, और CORS इस security restriction को bypass करने के लिए आया था। इसलिए जो काम चाहिए, उसके लिए CORS को enable करना है — इसे ऐसे स्वीकार करना आसान होता है
इसके उलट, जिन लोगों ने CORS के बाद web development सीखा, वे बस यह flow देखते हैं: cross-origin request की कोशिश, browser का उसे disallow करना, CORS preflight की कोशिश, और fail होने पर console में CORS error दिखना। अगर internal working नहीं पता और documentation नहीं पढ़ी, तो अंदाज़े से लगता है कि CORS ही request को रोकने की वजह है, और फिर लोग “CORS को disable” करने की कोशिश करते हैं। लेकिन CORS समस्या की वजह नहीं, उसका समाधान है
यही गलतफ़हमी रखने वाले लोग tutorials और online discussions में इसे पूरे confidence से दोहराते हैं, इसलिए और confusion बढ़ता है
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
comments पढ़कर यह पक्का हुआ कि सिर्फ़ मैं ही ऐसा नहीं हूँ। कोई भी CORS को ठीक से नहीं समझ पाता क्योंकि यह बहुत complex है और इसमें काफ़ी टकराव हैं
standards और headers भी लगातार बदलते रहते हैं, इसलिए developers आम तौर पर इधर-उधर चीज़ें tweak करते रहते हैं जब तक कि यह चलने न लगे, फिर product deploy करके आगे बढ़ जाते हैं। चलने के बाद भी developer console में errors और warnings रह सकती हैं, लेकिन बाहर से सब ठीक दिखे तो लोग उसे छेड़ते नहीं
CORS को समझने के लिए पहले same-origin policy को समझना ज़रूरी है
ख़ासकर अगर “यह ज़रूरी क्यों है?” समझना मुश्किल लग रहा हो, तो यहाँ से शुरू करना बेहतर है: https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
मैंने पहले same-origin policy को interview question के रूप में इस्तेमाल किया है, लेकिन बहुत से candidates इससे परिचित नहीं थे, इसलिए उस सवाल से ज़्यादा useful जानकारी नहीं मिलती थी
अगर किसी ने web apps बनाए हैं, तो किसी न किसी बिंदु पर उसे same-origin policy से सामना होना चाहिए था। अगर नहीं पता, तो फिर आम तौर पर मैं यह पूछता हूँ कि उन्होंने backend से communication कैसे किया। क्या उन्होंने CORS issue देखा था लेकिन बस सबसे तेज़ workaround लगाकर भूल गए, या सच में उसे समझने की कोशिश की — कुछ roles में यह एक useful signal हो सकता है
backend roles के लिए यह उतना उपयुक्त नहीं है। हर backend developer ने ऐसे frontend team के साथ क़रीब से काम नहीं किया होता जो अक्सर CORS issues झेलती हो
CORS के बारे में मुझे सबसे ज़्यादा यही याद रहता है कि debugging उम्मीद से कहीं ज़्यादा लंबी चलती है, browser error messages जानबूझकर अधूरे लगते हैं, और शुरुआत में CORS errors को दूसरे failure modes से अलग पहचानना मुश्किल होता है
हाँ, अगर server CORS request को समझ नहीं पाता और अजीब response लौटाता है, तो वह आख़िरकार CORS failure के रूप में दिखाई दे सकता है
comments देखते हुए यह काफ़ी दिलचस्प लगा, तो जोड़ दूँ कि same-origin policy browser को ऐसी websites तक जानकारी लीक करने से रोकती है जिनके पास access permission नहीं है, और CORS उस protection को कमज़ोर करने की अनुमति देता है
उदाहरण के लिए, same-origin policy
example.comकोyoutube.comकी subscription list लेने से रोकती है। लेकिन CORS के साथ यह allow किया जा सकता है किexample.com,youtube.com/public/*तक पहुँच सकेइसका एक और उपयोग यह है कि backend API किसी दूसरे frontend के तहत चलकर data exfiltration की वजह न बने। जैसे असली service में user logged in है, लेकिन user
g00gle.comपर है, और सारी requests man-in-the-middle attack की चपेट में आ सकती हैं — ऐसी स्थिति को रोकने में यह मदद करता हैमैं भी उन्हीं लोगों में से एक हूँ। CORS ऐसा topic है जिसे समय-समय पर फिर से पढ़ना पड़ता है, और मैं इसे हमेशा भूल जाता हूँ, इसलिए दिमाग़ में ठीक से टिकता नहीं
शायद इसलिए कि मैं backend developer हूँ और CORS issues से लगभग कभी नहीं जूझता। जो चीज़ रोज़ इस्तेमाल नहीं होती, मैं उसे जल्दी भूल जाता हूँ
एक सामान्य दुनिया में error messages में “response header” या “meta tag” जैसी hints होतीं, लेकिन लगता है बड़े browser vendors ने पहेली-जैसे messages लिखने वालों को hire किया है। Chrome का “requested resource” थोड़ा बेहतर है, फिर भी काफ़ी cryptic है
बेहतर message कुछ ऐसा होना चाहिए कि
https://bank.comresource CORS headers न होने की वजह से cross-origin requests allow नहीं करता, या current origin CORS allowlist में नहीं है। साथ में network tab की preflight request और MDN link भी दिखना चाहिए। CSP के लिए भी यह साफ़ होना चाहिए कि इस page के CSP header की वजह से resource fetch नहीं हो सकता, और inspector में page request header या meta tag तक ले जाना चाहिएआख़िरकार यह अक्सर इस assumption पर टिका होता है कि server तक पहुँच सिर्फ़ unmodified browser requests से होगी। Zoom vulnerability इसलिए हुई क्योंकि client side पर CORS और CSP को bypass करना बहुत आसान था, और हाँ, Zoom बुरा, आलसी और मूर्ख था, लेकिन मुझे लगता है कि इस model को बनाए रखने वाली community की भी कुछ ज़िम्मेदारी है
मैं समझता हूँ कि same-origin policy browser को malicious scripts चलाकर जानकारी leak करने से कैसे रोकती है। यह भी समझता हूँ कि
Access-Control-Allow-Originheader के ज़रिए server यह घोषित करता है कि वह अतिरिक्त origins पर भरोसा करता है और इस तरह SOP को relax करता हैफिर भी मुझे अब तक
Access-Control-Allow-Headersheader का उद्देश्य समझ नहीं आता। यह browser security को बेहतर बनाता हुआ नहीं लगता, और server security तो बिल्कुल नहीं। सोचता हूँ क्या protocol designers ने इसे सिर्फ़ “completeness” के लिए जोड़ा था। संबंधित: https://stackoverflow.com/questions/17992042