2 poin oleh GN⁺ 4 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Kerentanan server web lokal Zoom menunjukkan bahwa batas keamanan dapat runtuh dengan mudah ketika banyak pengembang web salah memahami cara kerja CORS
  • Saat berkomunikasi dengan server lokal localhost:19421, Zoom menyampaikan kode status melalui ukuran gambar alih-alih AJAX, yang dapat ditafsirkan sebagai implementasi akal-akalan untuk menghindari CORS
  • Chrome menerapkan header CORS bahkan pada server web localhost, dan komunikasi frontend-backend di port localhost yang berbeda juga didukung oleh browser
  • Desain yang lebih aman adalah server lokal menyediakan REST API dan menetapkan Access-Control-Allow-Origin agar hanya JavaScript dari zoom.us yang dibatasi untuk mengaksesnya
  • Melewati kebijakan same-origin mungkin membuat kode berjalan, tetapi fitur server lokal yang memiliki hak istimewa bisa terekspos ke semua situs web di internet

Akal-akalan CORS yang dibuat server web lokal Zoom

  • Dalam pekerjaan konsultasi full-stack dengan pengembang dari berbagai skala dan industri, berulang kali terlihat bahwa banyak pengembang web tidak memahami CORS
  • Dalam kerentanan Zoom terbaru, peneliti keamanan Jonathan Leitschuh menemukan bahwa Zoom menjalankan server web http://localhost:19421 di mesin pengguna
    • Ketika pengguna membuka tautan Zoom, situs web Zoom mengirim permintaan ke server web localhost untuk menjalankan aplikasi Zoom native
    • Alih-alih permintaan AJAX biasa, Zoom memuat gambar dari server web lokal, lalu menggunakan perbedaan ukuran gambar untuk mewakili kode kesalahan dan status server
  • Anggapan bahwa browser mengabaikan kebijakan CORS server localhost itu keliru, dan Chrome menghormati header CORS dari server web localhost
    • Saat frontend Create React App dan API backend dijalankan di port localhost yang berbeda, permintaan lintas origin juga terjadi, dan ini didukung oleh semua browser
  • Tampaknya Zoom menggunakan trik gambar untuk melewati CORS setelah permintaan AJAX diblokir
    • Akibatnya, bukan hanya situs web Zoom, tetapi juga situs web lain di internet dapat memicu perilaku klien native dan mengakses responsnya

Alternatif yang aman dan masalah UX yang masih tersisa

  • Implementasi yang aman adalah server web di localhost:19421 menerapkan REST API dan menetapkan nilai header Access-Control-Allow-Origin ke https://zoom.us
    • Dengan begitu, hanya JavaScript yang berjalan di domain zoom.us yang dapat berkomunikasi dengan server web localhost
    • zoom.us juga dapat memasang header Content Security Policy yang memblokir rendering iframe untuk mencegah rapat Zoom terbuka otomatis di latar belakang
  • Masalah bahwa halaman mana pun masih bisa mengalihkan browser ke tautan rapat zoom.us tetap ada
    • Namun, ini lebih dekat ke pengalaman pengguna yang dipilih Zoom daripada kerentanan perangkat lunak
    • Zoom melanggar ekspektasi pengguna bahwa saat mengklik tautan, kamera dan mikrofon tidak akan tiba-tiba terbuka untuk orang yang tidak dikenal
    • Jika ingin menghindari popup bawaan browser karena alasan UX, aplikasi juga bisa menampilkan popup sendiri, dan Google Meet menggunakan pendekatan itu dengan baik
  • Menjalankan server web di localhost sendiri sudah merupakan upaya yang berisiko, dan terutama jangan sampai menyediakan fitur berhak istimewa seperti instalasi perangkat lunak kepada semua situs web di internet
    • CORS adalah mekanisme untuk menangani situasi seperti ini dengan aman, jadi seharusnya tidak dilewati

Kebingungan CORS bukan hanya kesalahan Zoom

  • Tidak pasti apakah Zoom memilih pendekatan ini karena benar-benar tidak memahami CORS
    • lerunicorn di Reddit berpendapat bahwa Firefox mungkin memblokir XHR dari origin aman ke origin tidak aman
    • Namun, Firefox mendukung ini ketika origin-nya adalah localhost
    • Aplikasi native dapat membuat sertifikat self-signed uniknya sendiri, dan juga bisa menggunakan ekstensi browser
    • Dalam kasus apa pun, itu bukan alasan yang sah untuk menghilangkan pemfilteran origin
  • Kebingungan CORS bukan hanya masalah Zoom
  • Pengembang ingin membuat kode mereka berjalan, tetapi jika kebijakan same-origin dilewati sepenuhnya, seperti dalam kasus Zoom, hak akses lokal dapat terekspos ke situs web eksternal
  • Kebingungan soal CORS terlihat baik pada pengembang berpengalaman maupun pemula, dan meski belum jelas apakah API CORS terlalu rumit atau pendidikan tentang CORS dan CSP kurang memadai, cara yang sekarang jelas tidak bekerja dengan baik

1 komentar

 
GN⁺ 4 jam lalu
Komentar Hacker News
  • Tampaknya TFA juga tidak benar-benar memahami CORS atau menjelaskannya dengan sangat keliru
    Access-Control-Allow-Origin: https://zoom.us tidak menjamin bahwa hanya JavaScript dari domain zoom.us yang bisa berkomunikasi dengan server localhost. JavaScript dari situs lain pun tetap bisa mengirim request yang sama ke localhost:19421. CORS bukan mekanisme untuk membatasi sesuatu, melainkan untuk melonggarkan pembatasan bawaan. Header ini hanya memungkinkan JavaScript yang berjalan di zoom.us untuk membaca respons dari localhost:19421; request-nya sendiri tetap akan terjadi, jadi backend harus dibuat agar tidak menimbulkan efek samping

    • Saya tidak mengerti kenapa ini jadi komentar dengan upvote terbanyak. OP benar, dan penjelasan di atas salah
      Request GET memang akan dikirim, tetapi secara semestinya harus idempoten, jadi jika server diimplementasikan dengan benar, request itu tidak boleh menimbulkan efek samping, dan pada GET yang penting adalah apakah responsnya bisa dibaca. Sebaliknya, untuk request non-idempoten yang bisa memiliki efek samping, dalam situasi lintas origin browser lebih dulu mengirim preflight OPTIONS alih-alih request sebenarnya, dan jika respons OPTIONS tidak memiliki header yang benar, request sebenarnya tidak akan dikirim
    • Saya juga rasa tidak bisa dibilang CORS memang berfungsi seperti itu
      Kesalahpahaman tentang CORS sudah terlalu luas, dan dokumentasinya pun sering saling bertentangan, jadi sulit berharap pihak yang tidak dikenal sudah mengimplementasikannya dengan benar. Jika sebuah protokol menimbulkan kebingungan seluas ini, meskipun satu sisi berperilaku benar, tidak ada jaminan sisi lain juga begitu. Jika orang-orang terus memperbaiki kodenya sampai cocok dengan implementasi lain, akhirnya jadi kabur apakah yang salah ada di pihak sendiri atau pihak lawan
    • Sejauh yang saya pahami, tujuan utama preflight OPTIONS adalah mencegah request HTTP yang pada dasarnya tidak diizinkan, dan untuk request yang memang diizinkan sejak awal, CORS tidak melakukan apa-apa
      Misalnya, POST dengan Content-Type text/json tidak bisa dikirim ke host pihak ketiga tanpa preflight OPTIONS, tetapi POST dengan multipart/form-data diperbolehkan dan tidak diblokir oleh CORS. Dan jika endpoint tidak memeriksa Content-Type secara ketat lalu mengasumsikannya sebagai JSON, maka situs web mana pun pada dasarnya bisa mengirim POST tanpa interaksi pengguna
    • Asumsi “kita hanya membicarakan metode yang aman” adalah asumsi yang cukup besar
      Web developer yang layak seharusnya tidak membuat GET/HEAD/OPTIONS mengubah state, dan hal seperti bergabung ke rapat jelas merupakan perubahan state. PUT/DELETE juga harus idempoten. API POST dengan format selain JSON atau form harus memeriksa header Content-Type, dan POST dengan PUT/PATCH/DELETE serta Content-Type non-form akan memicu preflight sehingga CORS diperiksa sebelum request sebenarnya sampai ke server
    • Bagian tulisan yang mengatakan “aplikasi native dapat membuat sertifikat self-signed yang unik” juga bermasalah
      Hanya membuat sertifikat tidak cukup; sertifikat itu harus dipasang sebagai sertifikat root CA di semua trust store browser pada mesin tersebut. Jika private key root CA tidak dilindungi dengan benar, situs web mana pun bisa melakukan serangan man-in-the-middle, jadi setidaknya perlu pembatasan nama (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10). Namun Chrome tidak mendukung ini pada root CA sampai v112 pada 2023 (https://alexsci.com/blog/name-non-constraint/), sehingga perlu menambahkan CA perantara dan menerapkan pembatasan di sana. Tentu saja, kunci root CA sebaiknya dibuang
      Dulu saya pernah menambahkan basic constraints pada proyek yang memakai root CA lokal, tetapi saya menaruhnya di root CA yang salah dan bahkan tidak mengujinya di semua browser
  • Andai lebih banyak orang membaca dokumentasi CORS di MDN. Itu sangat membantu saya saat mencoba memahami CORS, dan melihat komentar di sini saya baru sadar banyak orang ternyata sesulit itu memahaminya
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS

    • Dokumen itu saja sudah menjawab sebagian besar hal, bukan cuma kasus origin sederhana tetapi juga cara kerja preflight
  • Yang sulit dipahami bukan hanya CORS, tetapi juga banyak developer yang tidak benar-benar memahami threat model
    Bahkan setelah mendengar penjelasannya, sering kali tetap sulit merasakan kenapa ini masalah besar. Terutama karena backend developer sering yang diminta mengatur CORS, padahal CORS bukan mekanisme perlindungan hak akses, jadi dari sudut pandang backend ini sering tampak tidak terlalu penting. Bagi penyerang terasa seperti mereka toh tidak bisa mengambil apa pun, dan dari sisi frontend ini mudah terlihat sebagai penghalang yang merepotkan. Tulisan ini menunjukkan contoh yang konkret dengan baik

    • Bahkan di proyek tempat developer yang sama menulis frontend dan backend, saya pernah melihat pengaturan CORS yang salah
      Sebagai operator, saya akhirnya memperbaikinya dengan benar di load balancer, dan setidaknya aplikasinya sekarang berjalan. CORS memang sulit dipahami, tetapi lebih menyedihkan lagi bahwa banyak developer tidak memahami bukan hanya threat model yang coba dicegah CORS, melainkan juga pengembangan web secara umum, khususnya protokol HTTP
    • Threat model CORS sebenarnya tidak terlalu sulit. Situasinya adalah penyerang mengarahkan pengguna ke situs miliknya agar pengguna melakukan tindakan tertentu terhadap situs Anda
    • CORS membingungkan karena dibangun di atas model izin bawaan yang cukup aneh. multipart/form-data boleh, tetapi JavaScript aplikasi tidak, semacam itu
    • Dari sudut pandang penyerang dan pembela, threat model ini tidak terasa sepenuhnya alami
      CORS bersifat opsional, dan library atau tool lain bisa saja mengabaikannya. Pada praktiknya CORS hanya bermakna untuk mencegah XSS dan CSRF terhadap pengguna manusia yang benar-benar sedang login; untuk skenario serangan lain, itu tidak berguna karena pelaku toh akan memakai skrip atau program yang bisa memalsukan header HTTP. Karena itu orang akhirnya menyalakan semua opsi CORS, yang justru menjadi skenario terburuk karena memungkinkan XSS dan CSRF
    • CORS sangat bagus untuk mencegah orang mencuri bandwidth dan sumber daya hosting dengan mudah. Untuk mencurinya mereka harus membuat proxy sendiri, dan itu jadi lebih mudah diblokir
  • Kolom komentar ini benar-benar terlihat sangat rendah kualitas informasinya, dan justru membuktikan poin penulis apa adanya

    • Mungkin juga ini perbedaan generasi
      Jika pernah mengembangkan web sebelum CORS muncul, Anda paham bahwa permintaan lintas domain pada dasarnya memang dilarang, dan CORS hadir untuk melewati pembatasan keamanan itu. Jadi mudah untuk menerima bahwa jika ingin melakukan sesuatu, Anda tinggal mengaktifkan CORS.
      Sebaliknya, orang yang belajar pengembangan web setelah era CORS hanya melihat alur: mencoba permintaan lintas origin, browser menilai itu tidak diizinkan, mencoba CORS preflight, lalu jika gagal muncul error CORS di konsol. Jika tidak tahu cara kerjanya di dalam dan tidak membaca dokumentasi lalu menebak-nebak, orang akan mengira CORS adalah penyebab permintaan diblokir dan berusaha “menonaktifkan CORS”. Padahal CORS bukan penyebab masalahnya, melainkan solusinya.
      Karena orang-orang dengan salah paham yang sama mengulanginya dengan percaya diri di tutorial dan diskusi online, jadinya makin membingungkan
    • CORS memang tidak intuitif, tetapi bisa dipahami jika membaca dokumentasinya
      https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
  • Membaca komentar-komentar ini membuat saya sadar saya bukan satu-satunya. Alasan tidak ada yang benar-benar memahami CORS adalah karena terlalu rumit dan penuh benturan.
    Standar dan header-nya juga terus berubah, jadi para developer biasanya cuma mengutak-atik ini-itu sampai akhirnya jalan, lalu produk dirilis dan selesai. Bahkan kalau sudah berfungsi pun, error dan peringatan bisa tetap tertinggal di konsol developer, tetapi kalau dari luar terlihat berjalan baik, orang biasanya tidak mau menyentuhnya lagi

  • Untuk memahami CORS, pertama-tama harus memahami same-origin policy
    Terutama kalau bagian “kenapa ini diperlukan?” terasa sulit, sebaiknya mulai dari sini: https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
    Dulu saya pernah memakai same-origin policy sebagai pertanyaan wawancara, tetapi banyak pelamar yang tidak akrab dengannya, jadi informasi yang bisa didapat dari pertanyaan itu ternyata sedikit

    • Menurut saya ini pertanyaan yang cukup bagus saat merekrut developer frontend
      Kalau seseorang sudah pernah mengembangkan web app, pada suatu titik ia seharusnya pernah berhadapan dengan same-origin policy. Kalau tidak tahu, biasanya saya jadi bertanya lebih jauh tentang bagaimana ia berkomunikasi dengan backend, dan sebagainya. Untuk beberapa peran, ini juga bisa menjadi sinyal yang berguna: apakah ia pernah menemui masalah CORS lalu hanya menerapkan jalan pintas tercepat dan melupakannya, atau benar-benar mencoba memahaminya.
      Untuk peran backend, ini kurang cocok. Tidak semua developer backend pernah bekerja sangat dekat dengan tim frontend yang sering menemui masalah CORS
  • Hal yang saya ingat tentang CORS adalah debugging-nya memakan waktu jauh lebih lama dari perkiraan, pesan error browser sengaja dibuat minim, dan pada awalnya error CORS sulit dibedakan dari mode kegagalan lain

    • Error CORS bukanlah “pesan error yang dikirim ke browser”, melainkan error yang dibuat browser karena memutuskan permintaan itu tidak boleh diizinkan.
      Tentu saja, jika server tidak memahami permintaan CORS dan mengembalikan respons aneh, itu pada akhirnya bisa diterjemahkan menjadi kegagalan CORS
  • Melihat kolom komentar ini cukup menarik, jadi saya tambahkan: same-origin policy melindungi agar browser tidak membocorkan informasi ke situs web yang tidak punya hak akses, dan CORS memungkinkan perlindungan itu dilemahkan
    Misalnya, same-origin policy mencegah example.com mengambil daftar langganan youtube.com. Namun dengan CORS, example.com bisa diizinkan mengakses youtube.com/public/*.
    Kegunaan lain adalah mencegah backend API berjalan di bawah frontend lain sehingga berujung pada pencurian data. Misalnya pengguna sebenarnya login ke layanan yang benar, tetapi sedang berada di g00gle.com, dan semua permintaan bisa disadap lewat serangan man-in-the-middle

    • Tepatnya justru kebalikannya. Yang mencegah masalah keamanan seperti ini adalah SOP, dan CORS adalah fitur yang melonggarkan SOP agar interaksi antar-aplikasi yang lebih kompleks bisa diizinkan
  • Saya juga termasuk orang seperti itu. CORS adalah topik yang harus dipelajari ulang secara berkala, dan saya selalu lupa sehingga tidak pernah benar-benar melekat di kepala.
    Mungkin karena saya developer backend dan hampir tidak pernah menghadapi masalah CORS. Hal-hal yang tidak dipakai setiap hari memang mudah saya lupakan

    • Pengalaman developer dengan CORS dan CSP itu mengerikan. Browser tidak benar-benar memberi tahu dari mana masalahnya berasal.
      Di dunia yang normal, pesan error akan memberi petunjuk seperti “response header” atau “meta tag”, tetapi browser vendor besar seolah mempekerjakan orang yang gemar menulis pesan teka-teki. “requested resource” milik Chrome masih mendingan, tetapi tetap terasa seperti sandi.
      Pesan yang lebih baik misalnya bisa mengatakan bahwa resource https://bank.com tidak mengizinkan permintaan lintas origin karena tidak memiliki header CORS, atau bahwa origin saat ini tidak ada dalam daftar yang diizinkan CORS. Seharusnya juga ditampilkan permintaan preflight di tab Network dan tautan ke MDN. Untuk CSP juga akan lebih baik jika disebutkan bahwa resource tidak bisa diambil karena header CSP pada halaman ini, lalu ditautkan ke header permintaan halaman atau meta tag di inspector
    • Masalah terbesar CORS adalah kebanyakan error tampak seperti masalah frontend, khususnya masalah browser, padahal perbaikannya sebenarnya harus dilakukan di backend
    • Saya juga merasakan hal yang mirip. Beberapa kali saya harus menangani CORS dalam situasi seperti “saya harus mengambil sesuatu dari server ini, tetapi saya tidak bisa mengubah CORS atau CSP servernya”, yang dalam istilah keamanan berarti “ada sistem keamanan dan saya harus mengakalinya”.
      Pada akhirnya ini biasanya bergantung pada asumsi bahwa server hanya akan diakses oleh permintaan browser yang tidak dimodifikasi. Kerentanan Zoom muncul karena di sisi klien terlalu mudah untuk melewati CORS dan CSP, dan memang benar Zoom itu buruk, malas, dan bodoh, tetapi saya juga merasa komunitas yang terus mempertahankan model seperti ini ikut bertanggung jawab
  • Saya paham bagaimana same-origin policy mencegah browser yang menjalankan skrip berbahaya membocorkan informasi, dan saya juga paham bahwa header Access-Control-Allow-Origin melonggarkan SOP dengan menyatakan bahwa server memercayai origin tambahan.
    Meski begitu, saya masih tidak mengerti tujuan header Access-Control-Allow-Headers. Rasanya itu tidak meningkatkan keamanan browser, apalagi keamanan server. Saya penasaran apakah perancang protokol memasukkannya hanya demi “kelengkapan”. Terkait: https://stackoverflow.com/questions/17992042