CORS (Cross-Origin Resource Sharing)

Untuk pembaca yang lahir setelah generasi milenial, silakan baca tentang The Corrs di Wikipedia

Topik CORS ini adalah topik security web yang sulit dimengerti bagi sebagian orang. Dalam pentest, kadang temuan CORS diperdebatkan mengenai severity-nya. Di tulisan ini saya akan berusaha menjelaskan apa itu CORS dan dampaknya untuk security.

Dasar Teori

Saya ingin sekali bisa menjelaskan CORS ini secara singkat, supaya nggak perlu ngetik panjang, tapi kenyataannya dibutuhkan pemahaman dulu mengenai request di browser. Jadi sebelum masuk masalah securitynya, saya jelaskan dulu mengenai CORS.

Sebelum ada XHR (XMLHTTPRequest)

Sebelum JavaScript mulai dipakai, yang bisa dibuat dengan HTML hanyalah menyisipkan konten dengan menggunakan tag yang ada, misalnya memakai <img src=""> atau <frame src="">. Konten ini (misalnya gambar) boleh berasal dari domain mana saja. Browser akan meminta konten dari domain lain. Hidup masih sangat sederhana.

Ketika JavaScript mulai dipakai, kemampuannya masih terbatas untuk memodifikasi DOM (Document Object Model). Ini pun masih belum jadi masalah. Javascript hanya sekedar membuat elemen HTML dan browser yang akan melakukan request ke server tujuan. JavaScript tidak memproses data secara langsung.

Lalu Microsoft membuat yang namanya XMLHTTPRequest, sekarang kode JavaScript bisa meminta data yang akand diproses tanpa harus ditampilkan di browser. Untuk data yang asalnya dari server yang sama, ini cukup sederhana, tapi biasanya jadi rumit ketika kita memakai domain/subdomain yang berbeda.

HTTP Header

Ketika melakukan request ke server, ada beberapa hal yang perlu dikirimkan:

  • VERB atau dikenal juga REQUEST METHOD, misalnya GET/POST/OPTIONS
  • Path (misalnya /admin, bisa juga mengandung parameter, misalnya /view?id=1)
  • Header (misalnya ‘User-Agent’, ‘Cookie’)
  • Data (untuk request yang mengirimkan data)

Di dalam bagian header inilah kita akan memasukkan Cookie dan juga header untuk CORS.

Cookie

Untuk sesuatu yang sifatnya publik, maka kita tidak butuh cookie. Misalnya kita pergi ke kebun binatang, bagian loket adalah tempat umum untuk bertanya, tidak perlu bayar kalau tidak masuk ke dalam.

Halaman umum, tidak butuh cookie

Cookie adalah data yang dikirim server ke client/browser. Lalu browser akan menyimpan data ini, dan ketika kita melakukan request berikutnya ke server yang sama maka Cookie akan dikirimkan oleh browser ke server tersebut.

Proses login. Server memberi Cookie

Secara awam, analogi cara kerja cookie itu seperti ini:

  • Kita daftar membership ke Gym dan mendapatkan kartu dengan nomor ID tertentu (ini seperti cookie). Waktu mendaftar mungkin kita ditanya informasi diri (Ini seperti kita memasukkan username/password di halaman login)
  • Ketika masuk Gym, kita cuma perlu menunjukkan kartu anggota, dan akan diterima selama dianggap masih aktif oleh gym
Meminta resource yang butuh cookie, browser mengirim Cookie

Jadi untuk orang yang tidak punya kartu anggota, ketika akan masuk Gym akan segera ditolak. Di dalam Browser, yang terjadi juga sama: jika tidak punya Cookie akan langsung ditolak.

Akses tanpa cookie ditolak untuk bagian account

Cookie memiliki masa aktif, dan server bisa memutuskan bahwa suatu cookie dianggap tidak aktif lagi karena suatu hal. Dalam hal ini: di browser masih ada Cookienya dan dikirim ke server, tapi server menolak Cookienya.

Mungkin kita punya banyak kartu untuk kunjungan ke dokter, salon, dan berbagai tempat lain. Tentunya kita akan cukup pintar: kalau ke gym ya menyerahkan kartu untuk Gym, bukan kartu yang dipakai dokter. Tapi mungkin Gym ini ada banyak cabang, jadi kartunya bisa dipakai juga untuk Gym lain.

Seperti halnya kartu membership yang kadang bisa dipakai di lebih dari satu tempat, cookie juga bisa dikirimkan browser ke situs lain. Ada aturannya yang lebih detail (misalnya apakah cookie bisa ke domain yang sama, ke protokol yang sama, ke path yang sama). Bagian ini tidak akan saya bahas dengan detail.

Perlu diperhatikan bahwa dengan sistem seperti ini: kalau ada yang bisa dapat kartu kita, maka bisa disalin dan kartunya dipakai orang lain. Ini sama juga dengan Cookie, jika kita bisa mendapatkan Cookie seseorang, maka (besar) kemungkinan kita bisa login menjadi orang tersebut.

Tentunya kita akan pusing jika kita punya puluhan kartu dan harus mengingat tiap tempat butuh kartu yang mana. Jadi bayangkan ada sebuah alat yang bisa otomatis mengurusi semua kartu kita, dan bisa pintar memberikan kartu yang benar ke tempat yang benar, dan juga akan membuang kartu otomatis jika sudah tidak berlaku lagi.

Contoh sebagian kecil Cookie di browser saya

Sebenarnya inilah yang dilakukan browser. Browser akan menyimpan cookie dan dengan pintar memberikan Cookie yang tepat ke website yang tepat, dan menghapus cookie yang sudah expire.

Kadang di suatu tempat (misalnya Kebun Binatang atau Dunia Fantasi), kita diberi tiket, dan itu hanya berlaku di hari itu saja, sampai kita keluar. Ini sebenarnya sama dengan Cookie yang hanya berlaku sampai timeout atau window ditutup.

Credentials

Cookie hanyalah salah satu dari credential yang mungkin dipakai oleh browser. Selain Cookie, ada 2 lagi yang bisa dipakai:

  • Header Authorization
  • SSL Certificate

Untuk menyederhanakan, saya akan menggunakan Cookie saja untuk mewakili semua credential yang mungkin.

Resource Sharing

Ini semua masih cukup jelas dan sederhana jika setiap domain memakai cookie untuk domainnya sendiri. Tapi ini mulai jadi rumit ketika skrip di sebuah domain membutuhkan akses dari origin berbeda.

Mari kita teruskan analogi tadi, misalnya Gym ini dibolehkan menghubungi dokter. Ini bisa sekedar memesan brosur dari dokter untuk ditampilkan di lobi. Dalam kasus ini siapa saja memang boleh meminta brosur dari dokter, dan brosur ini tidak bisa diedit teksnya oleh Gym. Ini seperti website meminta gambar dari site lain.

Simple request diijinkan tanpa Cookie dan tanpa batasan

Sekarang bagaimana jika permintaannya lebih rumit: kita ingin minta file Excel dari dokter yang bisa kita proses. Misalnya daftar tips selama bulan puasa untuk ditampilkan di TV yang ada di dalam Gym. Dalam kasus ini: meskipun ini data publik, tapi belum tentu diijinkan oleh dokternya.

Tapi mungkin kita ingin mengijinkan permintaan yang lebih spesifik tentang diri kita. Bayangkan pihak Gym bisa menghubungi dokter untuk meminta data kesehatan kita untuk menyarankan olahraga terbaik. Caranya: Gym akan meminta koneksi ke dokter, via alat ajaib yang kita miliki, dan alat ajaib ini akan menghubungi dokter dengan kartu kita untuk dokter tersebut.

Mari kita bayangkan dulu seperti apa analogi di browser. Kita sudah login di sebuah situs. Misalnya site1. Jadi browser punya cookie untuk site1, dan jika ada request ke site1, maka Cookie akan otomatis dikirim.

Cookie site1 akan dikirim ke site1 untuk tiap request

Nah bagaimana jika kita mengunjungi site2, tapi site ini ada JavaScriptnya, dan script tersebut mengirimkan request ke site2? Kalau hanya sekedar meminta gambar dari site2, browser akan segera mengirimkan requestnya. Tapi jika yang meminta dari JavaScript, maka ini akan melalui proses lain.

Mengakses site2, tapi JavaScript meminta datadiri dari site1

Tentunya berbahaya sekali kalau semua tempat bisa menghubungi dokter, menggunakan kartu kita, karena bisa meminta data pribadi kita. Bahkan jika semua boleh meminta data, maka situs akan jadi berat. Jadi ada mekanisme keamanan untuk mencegah sembarang tempat bisa menghubungi tempat lain dengan atau tanpa memakai kartu pribadi kita.

Mekanisme keamanannya bentuknya seperti ini: sebelum Gym diijinkan menghubungi dokter dengan memakai kartu kita, maka alat ajaib ini akan menghubungi dulu dokter, nanya: ini Gym mau akses data pribadi user dengan kartunya user, boleh nggak? kalau dibolehkan, maka Gym ini bisa memakai alat kita untuk menghubungi dokter, dan alat akan otomatis mengirimkan kartu identifikasi ke dokter.

Di browser Request ini di browser disebut sebagai preflight request. Jadi sebelum request yang sesungguhnya dikirimkan, browser mengirim dulu request ke server, dan jika dibolehkan, maka baru akan mengirimkan requestnya.

Preflight request

Mekanisme keamanan yang memungkinkan berbagai pihak berbagi (sharing) data (resource) antara Origin yang berbeda (cross origin) — dalam contoh ini Gym dan Dokter — yang dilakukan oleh browser ini yang disebut dengan CORS.

Origin

Origin meliputi skema/protokol (http/https/protokol lain), nama domain, dan port. Deskripsi lengkap mengenai origin bisa dibaca di RFC. Secara sederhana: jika semuanya sama persis, maka dianggap dari origin yang sama. Jika ada yang berbeda maka dianggap origin berbeda (jadi walau protokol/domain sama, tapi port berbeda, maka dianggap origin berbeda).

Request Sederhana

Request sederhana (istilah teknisnya: simple request) adalah request yang responsenya tidak akan diproses oleh JavaScript, seperti jaman awal web dulu (segala macam GET/POST yang datanya tidak akan dibaca/proses oleh JS). Ada banyak syarat sebuah request dianggap sederhana. Semua syarat ini harus dipenuhi:

  • Methodnya harus GET, HEAD atau POST
  • Header tambahan yang dibolehkan (selain default dari browser): Accept, Accept-Language Content-Language dan Content-Type
  • Untuk Content-Type yang diperbolehkan hanya application/x-www-form-urlencoded, multipart/form-data dan text/plain
  • Jika memakai XMLHttpRequest, maka tidak boleh ada listenernya untuk upload
  • API ReadableStream tidak boleh dipakai di request

Request sederhana ini tidak butuh header CORS. Jadi tidak akan ada preflight request.

Preflight Request

Untuk request selain simple request, ketika ditanya: apakah Origin x, boleh mengakses resource di Origin lain, maka browser akan mengirimkan request dengan verb/request method “OPTIONS” dengan menyatakan apa “Origin” yang meminta seperti ini:

OPTIONS /path/resource
Origin: https://site1
Access-Control-Request-Method: POST
Access-Control-Request-Headers: header1, header2

Jadi sebelum browser mengirimkan request, maka browser bertanya dulu ke server: ini script di origin http://site1 mau mengirim request POST dengan header header1 dan header2 ke path /path/resource. Boleh nggak?

Dan server akan menjawab dengan berbagai header, yang intinya adalah mengijinkan atau tidak. Header yang dikirim adalah:

  • Access-Control-Allow-Origin yang menyatakan origin yang dibolehkan
  • Access-Control-Allow-Methods yang menyatakan method yang dibolehkan
  • Access-Control-Allow-Headers yang menyatakan header apa yang diijinkan
  • Access-Control-Allow-Credentials yang menyatakan apakah credential boleh dikirimkan

Contoh reply adalah seperti ini:

HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: https://site1
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Allow-Headers: Content-Type, header1, header2

Untuk kode javascript yang melakukan simple request, ini agak berbeda:

  • Request akan tetap dilakukan (tanpa preflight request)
  • Jika Access-Control-Allow-Origin mengijinkan, maka Javascript bisa mendapatkan balasannya

Masalah Security Access-Control-Allow-Origin

Yang menjadi masalah paling umum adalah mengenai Access-Control-Allow-Origin. Jika kita mulai membuat aplikasi yang mengakses resource di origin berbeda, maka aplikasinya tidak jalan jika server tidak membalas sama sekali preflight request.

Jadi biasanya seseorang akan membuat jawaban untuk preflight request ini, dan biasanya untuk memudahkan development maka dibuat: Pokoknya semuanya boleh. Karena malas mengurusi domain development, uat, production.

Access-Control-Allow-Origin: *

Salah satu yang akan dicoba pertama oleh programmer adalah menggunakan * untuk Access-Control-Allow-Origin. Hasilnya: semua situs bisa akses ini, tapi kemudian akan disadari bahwa ini tidak kompatibel dengan Access-Control-Allow-Credentials: true (tidak diperbolehkan).

Jadi untuk data yang memang mau dibiarkan terbuka di internet, bisa diakses oleh JavaScript di situs manapun, maka menggunakan * tidak apa-apa. Tapi ini bermasalah untuk situs internal. Bahayanya adalah: situs external bisa meminta data ke situs internal/intranet, walau terbatas tanpa Cookie/Credential. Misalnya ini mungkin bisa digunakan untuk membaca berita internal perusahaan.

Bahaya Access-Control-Allow-Origin: * untuk situs internal

Reflected Access-Control-Allow-Origin

Berikutnya: karena belum tahu domain mana saja yang akan diijinkan di produksi, biasanya yang dilakukan oleh programmer adalah memakai origin di pertanyaan untuk mengisi jawaban. Jadi kalau ditanya: “Apakah origin X boleh mengakses?” “Ya, domain X boleh mengakses”. Tidak peduli X-nya itu apa.

Nah setting ini berbahaya jika digabung dengan Access-Control-Allow-Credentials: true, yang artinya: Cookie/Credentials boleh dikirimkan. Artinya situs manapun bisa mengirim request ke situs tersebut, memakai Cookie/Credential user. Jika tanpa Access-Control-Allow-Credentials: true maka ini efeknya sama dengan origin *.

Jika request dari domain insecure http://example.com dibolehkan oleh situs https://example.com, maka ini juga berbahaya. Artinya jika ada orang bisa melakukan MITM terhadap domain non secure (hanya port 80, port 443 dipassthough ke domain asli) maka orang ini bisa mengakses domain yang secure.

Access-Control-Allow-Origin: null

Header ini agak berbahaya. Browser mungkin akan membolehkan dokumen HTML di lokal (yang tidak punya domain) mengakses situs yang mengirimkan Access-Control-Allow-Origin: null. Jadi sebaiknya server jangan mengembalikan null.

Error yang berhubungan dengan CORS

Programmer banyak yang bingung menangani error CORS. Ini melibatkan dua pihak: front end yang meminta request, dan backend yang memproses. Apa yang saya tulsikan di sini hanya versi singkat error yang mungkin. Untuk developer, sebaiknya bacalah petunjuk ini (CORS for Developers).

Error pertama adalah di bagian preflight request: Server mungkin tidak membalas, atau memberikan balasan yang salah. Dalam kasus ini, maka browser tidak akan mengirimkan request ke server.

CORS FTW : ProgrammerHumor

Server juga bisa salah implementasinya, misalnya jika kita memakai middleware yang menambahkan header otomatis. Padahal jika ada lebih dari 1 header Allow, maka akibatnya jadi error. Preflight request juga tidak boleh diredirect ke URL lain, jika diredirect maka tidak akan diproses oleh browser.

Penutup

Semoga tulisan ini cukup memperjelas apa itu CORS, dan apa risiko dalam kesalahan header CORS. Topik CORS ini memang gampang-gampang susah, sampai ada satu buku khusus membahas CORS saja. Untuk kasus security lain yang berhubungan dengan CORS, artikel dari PortSwigger ini cukup bagus.

Tinggalkan Balasan

This site uses Akismet to reduce spam. Learn how your comment data is processed.