Jelajahi komunikasi lintas asal yang aman menggunakan API PostMessage. Pelajari kapabilitas, risiko keamanan, dan praktik terbaiknya untuk mengurangi kerentanan di aplikasi web.
Komunikasi Lintas Asal: Pola Keamanan dengan API PostMessage
Di web modern, aplikasi sering kali perlu berinteraksi dengan sumber daya dari asal yang berbeda. Kebijakan Asal Sama (Same-Origin Policy/SOP) adalah mekanisme keamanan penting yang membatasi skrip untuk mengakses sumber daya dari asal yang berbeda. Namun, ada skenario sah di mana komunikasi lintas asal diperlukan. API postMessage menyediakan mekanisme terkontrol untuk mencapai ini, tetapi sangat penting untuk memahami potensi risiko keamanannya dan menerapkan pola keamanan yang sesuai.
Memahami Kebijakan Asal Sama (SOP)
Kebijakan Asal Sama adalah konsep keamanan fundamental di peramban web. Kebijakan ini membatasi halaman web untuk membuat permintaan ke domain yang berbeda dari domain yang menyajikan halaman web tersebut. Sebuah asal didefinisikan oleh skema (protokol), host (domain), dan port. Jika salah satu dari ini berbeda, maka asalnya dianggap berbeda. Sebagai contoh:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Ini semua adalah asal yang berbeda, dan SOP membatasi akses skrip langsung di antara mereka.
Memperkenalkan API PostMessage
API postMessage menyediakan mekanisme yang aman dan terkontrol untuk komunikasi lintas asal. API ini memungkinkan skrip untuk mengirim pesan ke jendela lain (misalnya, iframe, jendela baru, atau tab), terlepas dari asalnya. Jendela penerima kemudian dapat mendengarkan pesan-pesan ini dan memprosesnya sesuai kebutuhan.
Sintaks dasar untuk mengirim pesan adalah:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Referensi ke jendela target (misalnya,window.parent,iframe.contentWindow, atau objek jendela yang diperoleh dariwindow.open).message: Data yang ingin Anda kirim. Ini bisa berupa objek JavaScript apa pun yang dapat diserialisasi (misalnya, string, angka, objek, array).targetOrigin: Menentukan asal tujuan pengiriman pesan. Ini adalah parameter keamanan yang krusial.
Di sisi penerima, Anda perlu mendengarkan event message:
window.addEventListener('message', function(event) {
// ...
});
Objek event berisi properti berikut:
event.data: Pesan yang dikirim oleh jendela lain.event.origin: Asal dari jendela yang mengirim pesan.event.source: Referensi ke jendela yang mengirim pesan.
Risiko dan Kerentanan Keamanan
Meskipun postMessage menawarkan cara untuk melewati batasan SOP, ia juga memperkenalkan potensi risiko keamanan jika tidak diimplementasikan dengan hati-hati. Berikut adalah beberapa kerentanan umum:
1. Ketidakcocokan Asal Target
Gagal memvalidasi properti event.origin adalah kerentanan kritis. Jika penerima secara buta mempercayai pesan tersebut, situs web mana pun dapat mengirim data berbahaya. Selalu verifikasi bahwa event.origin cocok dengan asal yang diharapkan sebelum memproses pesan.
Contoh (Kode Rentan):
window.addEventListener('message', function(event) {
// JANGAN LAKUKAN INI!
processMessage(event.data);
});
Contoh (Kode Aman):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Menerima pesan dari asal yang tidak tepercaya:', event.origin);
return;
}
processMessage(event.data);
});
2. Injeksi Data
Memperlakukan data yang diterima (event.data) sebagai kode yang dapat dieksekusi atau langsung menyuntikkannya ke dalam DOM dapat menyebabkan kerentanan Cross-Site Scripting (XSS). Selalu sanitasi dan validasi data yang diterima sebelum menggunakannya.
Contoh (Kode Rentan):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // JANGAN LAKUKAN INI!
}
});
Contoh (Kode Aman):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Implementasikan fungsi sanitasi yang tepat
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Implementasikan logika sanitasi yang kuat di sini.
// Sebagai contoh, gunakan DOMPurify atau pustaka serupa
return DOMPurify.sanitize(data);
}
3. Serangan Man-in-the-Middle (MITM)
Jika komunikasi terjadi melalui saluran yang tidak aman (HTTP), penyerang MITM dapat mencegat dan memodifikasi pesan. Selalu gunakan HTTPS untuk komunikasi yang aman.
4. Cross-Site Request Forgery (CSRF)
Jika penerima melakukan tindakan berdasarkan pesan yang diterima tanpa validasi yang tepat, penyerang berpotensi memalsukan pesan untuk menipu penerima agar melakukan tindakan yang tidak diinginkan. Terapkan mekanisme perlindungan CSRF, seperti menyertakan token rahasia dalam pesan dan memverifikasinya di sisi penerima.
5. Menggunakan Wildcard di targetOrigin
Mengatur targetOrigin ke * memungkinkan asal mana pun untuk menerima pesan. Ini harus dihindari kecuali benar-benar diperlukan, karena ini mengalahkan tujuan keamanan berbasis asal. Jika Anda harus menggunakan *, pastikan Anda menerapkan tindakan keamanan kuat lainnya, seperti kode otentikasi pesan (MAC).
Contoh (Hindari Ini):
otherWindow.postMessage(message, '*'); // Hindari menggunakan '*' kecuali benar-benar diperlukan
Pola Keamanan dan Praktik Terbaik
Untuk mengurangi risiko yang terkait dengan postMessage, ikuti pola keamanan dan praktik terbaik berikut:
1. Validasi Asal yang Ketat
Selalu validasi properti event.origin di sisi penerima. Bandingkan dengan daftar asal tepercaya yang telah ditentukan sebelumnya. Gunakan perbandingan ketat (===) untuk perbandingan.
2. Sanitasi dan Validasi Data
Sanitasi dan validasi semua data yang diterima melalui postMessage sebelum menggunakannya. Gunakan teknik sanitasi yang sesuai tergantung pada bagaimana data akan digunakan (misalnya, HTML escaping, URL encoding, validasi input). Gunakan pustaka seperti DOMPurify untuk sanitasi HTML.
3. Kode Otentikasi Pesan (MAC)
Sertakan Kode Otentikasi Pesan (MAC) dalam pesan untuk memastikan integritas dan keasliannya. Pengirim menghitung MAC menggunakan kunci rahasia bersama dan menyertakannya dalam pesan. Penerima menghitung ulang MAC menggunakan kunci rahasia bersama yang sama dan membandingkannya dengan MAC yang diterima. Jika cocok, pesan dianggap otentik dan tidak dirusak.
Contoh (Menggunakan HMAC-SHA256):
// Pengirim
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// Penerima
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Menerima pesan dari asal yang tidak tepercaya:', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Pesan otentik!');
processMessage(message); // Lanjutkan dengan memproses pesan
} else {
console.error('Verifikasi tanda tangan pesan gagal!');
}
}
Penting: Kunci rahasia bersama harus dibuat dan disimpan dengan aman. Hindari melakukan hardcoding kunci di dalam kode.
4. Menggunakan Nonce dan Stempel Waktu
Untuk mencegah serangan replay, sertakan nonce unik (angka yang digunakan sekali) dan stempel waktu dalam pesan. Penerima kemudian dapat memverifikasi bahwa nonce belum pernah digunakan sebelumnya dan stempel waktu berada dalam jangka waktu yang dapat diterima. Ini mengurangi risiko penyerang memutar ulang pesan yang sebelumnya dicegat.
5. Prinsip Hak Istimewa Terendah
Hanya berikan hak istimewa minimum yang diperlukan ke jendela lain. Misalnya, jika jendela lain hanya perlu membaca data, jangan izinkan untuk menulis data. Rancang protokol komunikasi Anda dengan mempertimbangkan prinsip hak istimewa terendah.
6. Kebijakan Keamanan Konten (CSP)
Gunakan Kebijakan Keamanan Konten (CSP) untuk membatasi sumber dari mana skrip dapat dimuat dan tindakan yang dapat dilakukan skrip. Ini dapat membantu mengurangi dampak kerentanan XSS yang mungkin timbul dari penanganan data postMessage yang tidak tepat.
7. Validasi Input
Selalu validasi struktur dan format data yang diterima. Tentukan format pesan yang jelas dan pastikan bahwa data yang diterima sesuai dengan format ini. Ini membantu mencegah perilaku dan kerentanan yang tidak terduga.
8. Serialisasi Data yang Aman
Gunakan format serialisasi data yang aman, seperti JSON, untuk melakukan serialisasi dan deserialisasi pesan. Hindari menggunakan format yang memungkinkan eksekusi kode, seperti eval() atau Function().
9. Batasi Ukuran Pesan
Batasi ukuran pesan yang dikirim melalui postMessage. Pesan yang besar dapat menghabiskan sumber daya yang berlebihan dan berpotensi menyebabkan serangan penolakan layanan (denial-of-service).
10. Audit Keamanan Reguler
Lakukan audit keamanan reguler pada kode Anda untuk mengidentifikasi dan mengatasi potensi kerentanan. Berikan perhatian khusus pada implementasi postMessage dan pastikan bahwa semua praktik terbaik keamanan diikuti.
Skenario Contoh: Komunikasi Aman Antara Iframe dan Induknya
Pertimbangkan skenario di mana sebuah iframe yang di-hosting di https://iframe.example.com perlu berkomunikasi dengan halaman induknya yang di-hosting di https://parent.example.com. Iframe tersebut perlu mengirim data pengguna ke halaman induk untuk diproses.
Iframe (https://iframe.example.com):
// Buat kunci rahasia bersama (ganti dengan metode pembuatan kunci yang aman)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// Dapatkan data pengguna
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Kirim data pengguna ke halaman induk
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
Halaman Induk (https://parent.example.com):
// Kunci rahasia bersama (harus cocok dengan kunci iframe)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Menerima pesan dari asal yang tidak tepercaya:', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Pesan otentik!');
// Proses data pengguna
console.log('Data pengguna:', userData);
} else {
console.error('Verifikasi tanda tangan pesan gagal!');
}
});
Catatan Penting:
- Ganti
YOUR_SECURE_SHARED_SECRETdengan kunci rahasia bersama yang dibuat dengan aman. - Kunci rahasia bersama harus sama di iframe dan halaman induk.
- Contoh ini menggunakan HMAC-SHA256 untuk otentikasi pesan.
Kesimpulan
API postMessage adalah alat yang ampuh untuk memungkinkan komunikasi lintas asal dalam aplikasi web. Namun, sangat penting untuk memahami potensi risiko keamanan dan menerapkan pola keamanan yang sesuai untuk mengurangi risiko ini. Dengan mengikuti pola keamanan dan praktik terbaik yang diuraikan dalam panduan ini, Anda dapat menggunakan postMessage dengan aman untuk membangun aplikasi web yang kuat dan aman.
Ingatlah untuk selalu memprioritaskan keamanan dan tetap mengikuti perkembangan praktik terbaik keamanan terbaru untuk pengembangan web. Tinjau kode dan konfigurasi keamanan Anda secara teratur untuk memastikan bahwa aplikasi Anda terlindungi dari potensi kerentanan.