Masa Lalu dan Masa Depan Komunikasi Cross-Origin: Menggali document.domain dan Alternatif Modern yang Aman
Dalam dunia pengembangan web, komunikasi antara berbagai bagian aplikasi—terutama yang berjalan di domain berbeda atau subdomain—adalah hal yang umum. Namun, hal ini juga menjadi salah satu area paling rawan kerentanan keamanan. Dulu, ada satu properti JavaScript yang sering digunakan untuk memfasilitasi komunikasi ini: document.domain.
Jika Anda pernah bekerja dengan aplikasi web lama atau codebase yang kompleks, kemungkinan besar Anda pernah menemukan atau bahkan menggunakan document.domain. Properti ini memungkinkan developer untuk “merelaksasi” kebijakan keamanan browser yang fundamental, yaitu Same-Origin Policy (SOP). Tapi, seperti banyak alat lama di web, apa yang dulunya praktis kini menjadi sumber masalah keamanan dan sudah usang.
Artikel ini akan membawa Anda menyelami sejarah document.domain, mengapa ia menjadi berbahaya, dan yang terpenting, bagaimana kita bisa beralih ke alternatif modern yang lebih aman dan efisien. Jika aplikasi Anda masih mengandalkan document.domain, atau Anda sekadar ingin memahami lebih dalam tentang keamanan komunikasi cross-origin, Anda berada di tempat yang tepat!
1. Sekilas tentang Same-Origin Policy (SOP)
Sebelum kita menyelami document.domain, mari kita pahami dulu fondasi keamanannya: Same-Origin Policy (SOP). 📌 SOP adalah fitur keamanan penting di browser yang membatasi bagaimana dokumen atau skrip yang dimuat dari satu “origin” dapat berinteraksi dengan sumber daya dari “origin” lain.
Origin didefinisikan oleh kombinasi protokol (misalnya http, https), host (misalnya www.example.com), dan port (misalnya 80, 443). Jika salah satu dari ketiga elemen ini berbeda, maka dua sumber daya dianggap berasal dari origin yang berbeda.
Tujuan utama SOP adalah mencegah skrip berbahaya di satu situs web mengakses data sensitif di situs web lain. Bayangkan jika sebuah situs web jahat bisa membaca data dari akun bank Anda yang sedang terbuka di tab lain; itu adalah mimpi buruk keamanan yang dicegah oleh SOP.
SOP memengaruhi banyak operasi, termasuk:
- Akses ke DOM dari
<iframe> - Permintaan AJAX (XMLHttpRequest dan Fetch API)
- Akses ke data yang disimpan di browser (LocalStorage, IndexedDB)
2. document.domain: Sejarah, Cara Kerja, dan Masalahnya
Dulu, SOP seringkali terasa terlalu ketat, terutama untuk aplikasi yang kompleks yang menggunakan banyak subdomain, seperti app.example.com dan api.example.com. Developer ingin agar app.example.com bisa berkomunikasi langsung dengan api.example.com tanpa batasan SOP, karena keduanya secara logis adalah bagian dari aplikasi yang sama.
Di sinilah document.domain berperan. Properti ini memungkinkan skrip untuk mengatur domain dari dokumen saat ini ke domain induk yang lebih umum. Misalnya, jika app.example.com dan api.example.com sama-sama mengatur document.domain = "example.com", maka browser akan memperlakukan keduanya seolah-olah berasal dari origin yang sama, yaitu example.com. Ini secara efektif “merelaksasi” SOP.
Contoh Cara Kerja document.domain (Era Lama):
// Di app.example.com
document.domain = "example.com";
// Di iframe yang dimuat dari api.example.com
// Setelah iframe juga mengatur document.domain = "example.com"
// Maka app.example.com dan iframe bisa saling mengakses DOM
// atau memanggil fungsi JavaScript secara langsung.
Masalah document.domain: Gerbang Kerentanan
Meski terdengar praktis, document.domain memiliki cacat fundamental yang serius:
- Global State dan Privilege Escalation: Ketika Anda mengatur
document.domain, Anda memengaruhi semua frame dan window lain yang juga mengatur domain yang sama. Ini berarti jika ada satu subdomain (misalnyamalicious.example.com) yang berhasil disusupi dan mengaturdocument.domain = "example.com", ia bisa mendapatkan akses ke dokumen dari semua subdomain lain yang juga telah mengaturdocument.domain = "example.com", termasukapp.example.comataubank.example.com(jika ada). Ini adalah bentuk privilege escalation yang berbahaya. - Mengurangi Isolasi: Tujuan SOP adalah isolasi.
document.domainsecara langsung mengikis isolasi ini, membuka celah bagi serangan Cross-Site Scripting (XSS) atau eksfiltrasi data jika salah satu bagian dari domain induk disusupi. - Deprekasi dan Pembatasan Modern: Browser modern semakin membatasi atau bahkan mendeprekasi penggunaan
document.domain. Sejak Chrome 109, Firefox 119, dan Safari 16.4, pengaturandocument.domaintelah dinonaktifkan secara default di dalam<iframe>yang di-embed dari origin yang berbeda. Ini berarti jika frame induk dan frame child berada di subdomain yang berbeda, mereka tidak lagi dapat menggunakandocument.domainuntuk berkomunikasi. Jika Anda mencoba mengaturnya, akan ada pesan error di konsol.
⚠️ Peringatan: Penggunaan document.domain sangat tidak dianjurkan untuk aplikasi web modern. Jika Anda masih menggunakannya, segera cari alternatif.
3. Mengapa document.domain Berbahaya? Studi Kasus Sederhana
Bayangkan sebuah skenario:
Anda memiliki aplikasi e-commerce (shop.example.com) yang mengandalkan document.domain = "example.com" untuk berkomunikasi dengan payment.example.com (juga mengatur document.domain = "example.com").
Di sisi lain, Anda juga memiliki situs blog lama (blog.example.com) yang kurang terawat, dan secara tidak sengaja mengizinkan User-Generated Content (UGC) tanpa sanitasi yang memadai. Seorang penyerang berhasil menyuntikkan skrip XSS ke blog.example.com.
Skrip XSS di blog.example.com bisa melakukan hal berikut:
- Mengatur
document.domain = "example.com". - Mencari
<iframe>darishop.example.comataupayment.example.comyang juga telah mengaturdocument.domain = "example.com". - Setelah domain disesuaikan, skrip jahat dari
blog.example.comkini memiliki akses penuh ke DOM dan JavaScript di<iframe>yang rentan. Ini berarti penyerang bisa membaca informasi pribadi pengguna (nama, alamat, detail kartu kredit), mengubah isi halaman, atau bahkan melakukan tindakan atas nama pengguna.
❌ Ini menunjukkan bagaimana satu titik lemah (blog.example.com) bisa membahayakan seluruh ekosistem domain induk karena document.domain merusak batas keamanan yang seharusnya ada.
4. Alternatif Modern dan Aman untuk Komunikasi Cross-Origin
Untungnya, web modern menyediakan banyak API yang lebih aman dan terstruktur untuk komunikasi cross-origin. Mari kita bahas beberapa di antaranya:
4.1. Window.postMessage(): Komunikasi Aman Antar Window/Frame
Window.postMessage() adalah standar emas untuk komunikasi antar window, frame, atau worker dari origin yang berbeda. Ia memungkinkan pengiriman pesan secara asinkron dengan kontrol ketat atas origin pengirim dan penerima.
Cara Kerja:
- Anda memanggil
postMessage()pada objekWindowtarget (misalnya,iframe.contentWindow.postMessage()). - Anda menyertakan data yang ingin dikirim dan target origin sebagai argumen. Target origin ini sangat penting untuk keamanan. Anda harus selalu menentukan origin yang spesifik (bukan
*) kecuali Anda benar-benar ingin mengirim ke origin mana pun. - Penerima akan mendengarkan event
messagepada objekWindowmereka. Dalam handler event, mereka harus selalu memverifikasi origin pengirim (event.origin) untuk memastikan pesan berasal dari sumber yang terpercaya.
Contoh Kode (postMessage):
// ====== Di Parent (misal: app.example.com) ======
const iframe = document.getElementById('myIframe');
const targetOrigin = 'https://api.example.com'; // ✅ Selalu tentukan origin spesifik
// Mengirim pesan ke iframe
iframe.contentWindow.postMessage({ type: 'DATA_REQUEST', payload: 'user_id_123' }, targetOrigin);
// Menerima pesan dari iframe
window.addEventListener('message', (event) => {
// ⚠️ Sangat penting: Verifikasi origin pengirim!
if (event.origin !== targetOrigin) {
console.warn('Pesan diterima dari origin tidak dikenal:', event.origin);
return;
}
if (event.data.type === 'DATA_RESPONSE') {
console.log('Data dari iframe:', event.data.payload);
}
});
// ====== Di Iframe (misal: api.example.com) ======
window.addEventListener('message', (event) => {
// ⚠️ Sangat penting: Verifikasi origin pengirim!
const expectedOrigin = 'https://app.example.com';
if (event.origin !== expectedOrigin) {
console.warn('Pesan diterima dari origin tidak dikenal:', event.origin);
return;
}
if (event.data.type === 'DATA_REQUEST') {
console.log('Menerima permintaan data:', event.data.payload);
const responseData = { user: 'John Doe', balance: 1000 };
// Mengirim balasan ke parent
event.source.postMessage({ type: 'DATA_RESPONSE', payload: responseData }, event.origin);
}
});
✅ postMessage adalah pilihan terbaik karena secara eksplisit memaksa Anda untuk mempertimbangkan dan memverifikasi origin,