Geliştiriciler CORS'u Anlamıyor (2019)
(fosterelli.co)- Zoom'un yerel web sunucusu zafiyeti, birçok web geliştiricisinin CORS'un nasıl çalıştığını yanlış anladığında güvenlik sınırlarının ne kadar kolay çözülebileceğini gösteriyor
- Zoom,
localhost:19421yerel sunucusuyla iletişim kurarken AJAX yerine durum kodlarını görüntü boyutu üzerinden iletti; bu da CORS'tan kaçınmaya yönelik bir geçici çözüm uygulaması olarak yorumlanıyor - Chrome, localhost web sunucularında da CORS başlıklarını uygular ve farklı localhost portlarındaki frontend-backend iletişimi de tarayıcıda desteklenir
- Daha güvenli tasarım, yerel sunucunun bir REST API sunması ve
Access-Control-Allow-Originayarlayarak erişimi yalnızca zoom.us JavaScript'ine kısıtlamasıdır - Aynı kaynak politikasını aşarsanız kod çalışabilir, ancak yerel sunucunun ayrıcalıklı işlevleri internetteki tüm web sitelerine açığa çıkabilir
Zoom yerel web sunucusunun yarattığı CORS baypası
- Farklı ölçek ve sektörlerden geliştiricilerle tam yığın danışmanlık sahasında çalışırken, web geliştiricilerinin CORS'u anlamaması sorununun tekrar tekrar ortaya çıktığı görüldü
- Yakın tarihli Zoom zafiyetinde güvenlik araştırmacısı Jonathan Leitschuh, Zoom'un kullanıcı makinesinde
http://localhost:19421web sunucusu başlattığını keşfetti- Kullanıcı bir Zoom bağlantısı açtığında Zoom web sitesi localhost web sunucusuna istek göndererek yerel Zoom uygulamasını çalıştırıyor
- Normal AJAX isteği yerine yerel Zoom web sunucusundan bir görüntü yükleniyor ve görüntünün farklı boyutlarıyla sunucunun hata/durum kodları ifade ediliyor
- Tarayıcının localhost sunucusunun CORS politikasını yok saydığı yorumu yanlıştı; Chrome, localhost web sunucusunun CORS başlıklarına saygı gösterir
- Create React App frontend'i ile backend API'si farklı localhost portlarında çalıştığında da çapraz kaynak isteği oluşur ve bu tüm tarayıcılarda desteklenir
- AJAX istekleri engellenince Zoom'un görüntü hilesiyle CORS'u aştığı anlaşılıyor
- Sonuç olarak yalnızca Zoom web sitesi değil, internetteki diğer web siteleri de yerel istemci davranışını tetikleyebilir ve yanıta erişebilir hale geldi
Güvenli alternatif ve kalan UX sorunu
- Güvenli uygulama,
localhost:19421üzerindeki web sunucusunun bir REST API uygulaması veAccess-Control-Allow-Originbaşlık değerinihttps://zoom.usolarak ayarlamasıdır- Böylece yalnızca zoom.us alan adında çalışan JavaScript, localhost web sunucusuyla iletişim kurabilir
- zoom.us, Zoom toplantısının arka planda otomatik açılmasını önlemek için iframe render etmeyi engelleyen bir Content Security Policy başlığı da koyabilir
- Herhangi bir sayfanın tarayıcıyı zoom.us toplantı bağlantısına yönlendirebilmesi sorunu ise hâlâ kalıyor
- Ancak bu, bir yazılım zafiyetinden çok Zoom'un seçtiği kullanıcı deneyimine daha yakın
- Kullanıcı bağlantıya tıkladığında kamera ve mikrofonun tanımadığı kişilere bir anda açılmayacağı yönündeki beklentiyi Zoom bozuyor
- Tarayıcı varsayılan açılır penceresinden UX nedeniyle kaçınmak isteniyorsa, uygulama içinde de açılır pencere gösterilebilir; Google Meet bu yaklaşımı iyi kullanıyor
- localhost üzerinde web sunucusu çalıştırmak başlı başına riskli bir girişimdir; özellikle yazılım kurulumu gibi ayrıcalıklı işlevler internetteki tüm web sitelerine sunulmamalıdır
- CORS bu tür durumları güvenli biçimde ele almak için vardır; bu yüzden baypas edilmemelidir
Yalnızca Zoom'a özgü olmayan CORS karmaşası
- Zoom'un gerçekten CORS'u anlamadığı için bu yaklaşımı seçip seçmediği net değil
- Reddit'teki lerunicorn, Firefox'un güvenli kaynaktan güvensiz kaynağa XHR'yi engelleyebileceğini düşünüyor
- Ancak Firefox, origin localhost olduğunda bunu destekler
- Yerel uygulamalar kendi benzersiz kendinden imzalı sertifikalarını üretebilir ve tarayıcı eklentileri de kullanılabilir
- Hiçbir durumda origin filtrelemesini atlamak için haklı bir gerekçe oluşmaz
- CORS karmaşası yalnızca Zoom'un sorunu değil
- Stack Overflow'da
Access-Control-Allow-Originile ilgili çok sayıda soru bulunuyor - Bazı Express örneklerinde aynen kopyalandığında uygulamayı zafiyetli hale getirebilecek güvensiz varsayılanlar öneriliyor
- Diğer üreticiler de Zoom ile aynı zafiyeti yaşamıştı
- Stack Overflow'da
- Geliştiriciler kodun çalışmasını ister, ancak aynı kaynak politikasını tamamen baypas etmek Zoom örneğinde olduğu gibi yerel ayrıcalıkların harici web sitelerine açılmasına yol açar
- CORS karmaşası hem deneyimli hem de yeni geliştiricilerde görülüyor; CORS API'sinin aşırı karmaşık olup olmadığı ya da CORS ve CSP eğitiminin yetersiz kalıp kalmadığı net değil, ancak mevcut yaklaşım iyi işlemiyor
1 yorum
Hacker News yorumları
Görünüşe göre TFA da CORS'u ya gerçekten anlamamış ya da ciddi biçimde yanlış açıklamış
Access-Control-Allow-Origin: https://zoom.us, yalnızca zoom.us alan adındaki JavaScript'in localhost sunucusuyla iletişim kurmasını garanti etmez. Başka web sitelerindeki JavaScript de aynı şekildelocalhost:19421adresine istek gönderebilir. CORS, bir şeyi kısıtlayan değil, varsayılan kısıtlamaları gevşeten bir mekanizmadır. Bu header sadece zoom.us'ta çalışan JavaScript'inlocalhost:19421yanıtını okuyabilmesini sağlar; isteğin kendisi ise her durumda gerçekleşebileceği için, backend tarafında yan etki oluşmaması gerekirGET istekleri gönderilir, ancak bunların normalde idempotent olması gerekir; dolayısıyla sunucu düzgün uygulanmışsa yan etki yaratmamalıdır ve GET'te asıl mesele yanıtın okunup okunamayacağıdır. Buna karşılık yan etki barındırabilen idempotent olmayan isteklerde, gerçek istekten önce preflight OPTIONS isteği gider ve OPTIONS yanıtında doğru header'lar yoksa gerçek istek gönderilmez
CORS hakkındaki yanlış anlamalar o kadar yaygın ve dokümantasyonlar da sık sık birbiriyle çelişiyor ki, bilinmeyen bir tarafın bunu doğru uygulamış olmasını beklemek zor. Bir protokol bu kadar yaygın kafa karışıklığı yaratıyorsa, bir taraf doğru çalışsa bile diğer tarafın da öyle davranıp davranmayacağı belirsizdir. İnsanlar kodu diğer implementasyonlarla çalışana kadar düzeltmişse, hatanın kendi taraflarında mı yoksa karşı tarafta mı olduğu da bulanıklaşır
Örneğin
Content-Typedeğeritext/jsonolan bir POST, OPTIONS preflight olmadan üçüncü taraf bir hosta gönderilemez; amamultipart/form-dataolan bir POST'a izin verilir ve CORS bunu engellemez. Uç nokta daContent-Type'ı sıkı biçimde kontrol etmeyip JSON varsayarsa, herhangi bir web sitesi kullanıcı etkileşimi olmadan POST gönderebilir hale gelirİyi bir web geliştiricisi GET/HEAD/OPTIONS'un durumu değiştirmesine izin vermemelidir ve toplantıya katılmak gibi bir şey durum değişikliğidir. PUT/DELETE de idempotent olmalıdır. JSON veya form olmayan türdeki POST API'leri
Content-Typeheader'ını kontrol etmelidir; ayrıcaPUT/PATCH/DELETEile form türü olmayanContent-Type'a sahip POST'lar preflight tetikler, böylece gerçek istek sunucuya ulaşmadan önce CORS kontrolü yapılırSadece sertifika oluşturmak yeterli değildir; makinedeki tüm tarayıcı güven depolarına root CA sertifikası olarak kurulması gerekir. Root CA'nın özel anahtarı düzgün korunmazsa, herhangi bir web sitesine karşı ortadaki adam saldırısı yapılabilir; bu yüzden en azından ad kısıtlaması gerekir(https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10). Ancak Chrome'da bu, 2023'te v112'ye kadar root CA'da çalışmıyordu(https://alexsci.com/blog/name-non-constraint/); bu yüzden araya bir intermediate CA ekleyip kısıtlamayı ona uygulamak gerekiyordu. Elbette root CA anahtarını atmak gerekir
Geçmişte yerel root CA kullanan bir projede basic constraints eklemiştim, ama bunu yanlışlıkla root CA'ya koymuştum ve tüm tarayıcılarda da test etmemiştim
Keşke daha fazla kişi MDN'in CORS dokümantasyonunu okusa. CORS'u anlamaya çalışırken bana çok yardımcı olmuştu; buradaki yorumlara bakınca insanların bunu bu kadar zor bulduğunu bilmiyordum
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
Zor olan sadece CORS değil; birçok geliştirici tehdit modelini de gerçekten anlamıyor
Açıklamayı duysanız bile bunun neden büyük bir sorun olduğunu kavramak çoğu zaman kolay olmuyor. Özellikle CORS ayarlarını çoğu zaman backend geliştiricileri yaptığı için, CORS bir erişim yetkisi koruma mekanizması olmadığından backend açısından çok önemli görünmeyebiliyor. Saldırganın veriyi alamayacağı düşünülüyor, frontend açısından ise can sıkıcı bir engel gibi görünüyor. Bu yazı somut örnekleri iyi gösteriyor
Operasyondan sorumlu kişi olarak bunu load balancer üzerinde yeniden doğru şekilde düzelttim ve en azından uygulama artık çalışıyor. CORS'u anlamak zor, ama daha üzücü olan, sadece CORS'un engellemeye çalıştığı tehdit modelini değil, genel olarak web geliştirmeyi, özellikle de HTTP protokolünü anlamayan geliştiricilerin çok olması
multipart/form-datatamam ama uygulama JavaScript'i değil gibi bir durum varCORS isteğe bağlıdır ve diğer kütüphane ya da araçlar bunu tamamen görmezden gelebilir. CORS aslında yalnızca gerçekten giriş yapmış bir insan kullanıcıya yönelik XSS ve CSRF'yi önlemede anlamlıdır; diğer saldırı senaryolarında zaten HTTP header'larını taklit eden script ya da programlar kullanıldığı için anlamsız kalır. Bu yüzden insanlar sonunda tüm CORS seçeneklerini açıyor; bu da XSS ve CSRF'ye izin verilen en kötü durumdur
Bu yorum bölümü gerçekten oldukça düşük bilgi seviyesinde görünüyor ve hatta yazarın ana fikrini olduğu gibi kanıtlıyor
CORS ortaya çıkmadan önce web geliştirme yaptıysanız, aslında çapraz alan adı isteklerinin yasak olduğunu ve CORS’un bu güvenlik kısıtını aşmak için ortaya çıktığını anlarsınız. Bu yüzden istenen şeyi yapmak için CORS’u etkinleştirmenin yeterli olduğunu kabul etmek kolaydır.
Buna karşılık, CORS sonrasında web geliştirmeyi öğrenen biri çapraz origin isteği yapmayı dener, tarayıcının buna izin vermediğini görür, CORS preflight dener, o da başarısız olursa konsolda CORS hatası görür. İç işleyişi bilmeden ve dokümantasyonu okumadan tahmin yürütürse, sorunun nedeninin CORS olduğunu düşünüp “CORS’u devre dışı bırakmaya” çalışır. Oysa CORS sorunun nedeni değil, çözümüdür.
Aynı yanlış anlamaya sahip kişiler bunu tutorial’larda ve çevrimiçi tartışmalarda kendinden emin biçimde tekrarladıkça kafa daha da karışıyor
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
Yorumları okurken bunun sadece bana özgü olmadığını görmüş oldum. Kimsenin CORS’u anlamamasının nedeni aşırı karmaşık ve çok fazla çatışmalı olması.
Standartlar ve header’lar da sürekli değiştiği için geliştiriciler genelde çalışana kadar bir şeyleri kurcalıyor, sonra ürünü yayına alıp bırakıyor. Çalışsa bile geliştirici konsolunda hata ve uyarılar kalabiliyor ama dışarıdan düzgün görünüyorsa genelde bir daha dokunulmuyor
CORS’u anlamak için önce same-origin policy’yi anlamak gerekir
Özellikle “Buna neden ihtiyaç var?” kısmı zorsa, başlamak için iyi yer burası: https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
Eskiden same-origin policy’yi mülakat sorusu olarak kullanmıştım ama adayların çoğu buna aşina olmadığından, o sorudan elde edilen bilgi sınırlı kalıyordu
Çünkü web uygulaması geliştirdiyseniz bir noktada mutlaka same-origin policy ile karşılaşmış olmanız gerekir. Bilmiyorsa genelde backend ile nasıl iletişim kurduğunu gibi şeyleri daha fazla sormaya başlıyorum. CORS sorunuyla karşılaşıp sadece en hızlı geçici çözümü mü uyguladığı, yoksa gerçekten anlamaya mı çalıştığı bazı roller için faydalı bir sinyal oluyor.
Backend rolleri için daha az uygun. Çünkü her backend geliştirici CORS sorunlarıyla sık karşılaşan frontend ekipleriyle yakın çalışmış olmuyor
CORS hakkında aklımda kalan şey, debug etmenin beklenenden çok daha uzun sürmesi, tarayıcı hata mesajlarının kasıtlı olarak yetersiz olması ve CORS hatalarının başlangıçta diğer başarısızlık türlerinden ayırt edilmesinin zor olması
Elbette sunucu CORS isteğini anlamayıp tuhaf bir yanıt döndürürse, bu sonuçta CORS başarısızlığına çevrilebilir
Yorum bölümüne bakınca oldukça eğlenceli geldiği için ekleyeyim: same-origin policy, tarayıcının erişim yetkisi olmayan web sitelerine bilgi sızdırmasını engeller; CORS ise bu korumayı gevşetebilmenizi sağlar
Örneğin same-origin policy,
example.com’unyoutube.comüzerindeki abonelik listesini almasını engeller. Ama CORS kullanılırsaexample.com’unyoutube.com/public/*alanına erişmesine izin verilebilir.Bir başka kullanım da backend API’nin başka bir frontend altında çalışıp veri sızıntısına yol açmasını engellemektir. Örneğin kullanıcı gerçek hizmette oturum açmıştır ama
g00gle.comüzerindedir ve tüm istekler ortadaki adam saldırısıyla ele geçirilebilir; buna karşı koruma sağlarBen de o insanlardan biriyim. CORS, düzenli olarak yeniden çalışılması gereken bir konu ve sürekli unuttuğum için aklımda pek kalmıyor.
Sanırım backend geliştiricisi olduğum için CORS sorunlarıyla neredeyse hiç karşılaşmıyorum. Her gün kullanmadığım şeyleri kolay unutuyorum
Normal bir dünyada hata mesajına “response header” ya da “meta tag” gibi ipuçları eklenirdi; ama büyük tarayıcı üreticileri sanki bilmece gibi mesajlar yazan insanları işe almış gibi görünüyor. Chrome’daki “requested resource” nispeten daha iyi ama o da hâlâ şifre gibi.
Daha iyi mesajlar örneğin
https://bank.comkaynağının CORS header’ı olmadığı için çapraz origin isteklerine izin vermediğini ya da mevcut origin’in CORS izin listesinde olmadığını söylemeli. Bunun yanında Network sekmesindeki preflight isteğini ve bir MDN bağlantısını da göstermeli. CSP için de bu sayfanın CSP header’ı yüzünden kaynağın alınamadığını söyleyip bunu denetleyicideki sayfa istek header’ına veya meta tag’e bağlaması daha iyi olurduSonuçta bu genelde sunucunun yalnızca değiştirilmemiş tarayıcı istekleriyle erişileceği varsayımına dayanıyor. Zoom açığı, istemci tarafında CORS ve CSP’yi atlatmanın fazla kolay olması yüzünden ortaya çıktı; Zoom’un kötü, tembel ve akılsız olduğu doğru ama bu modeli sürdürmeye devam eden topluluğun da sorumluluğu olduğunu düşünüyorum
same-origin policy’nin tarayıcının kötü amaçlı script çalıştırıp bilgi sızdırmasını nasıl engellediğini anlıyorum. Sunucunun
Access-Control-Allow-Originheader’ı ile ek origin’lere güvendiğini ilan ederek SOP’yi gevşetmesini de anlıyorum.Yine de
Access-Control-Allow-Headersheader’ının amacını hâlâ anlamıyorum. Tarayıcı güvenliğini iyileştiriyor gibi görünmüyor, sunucu güvenliğini ise hiç değil. Acaba protokol tasarımcıları bunu “tamlık” için mi ekledi diye merak ediyorum. İlgili: https://stackoverflow.com/questions/17992042